Skip to content

Commit 6437679

Browse files
committed
fix(sandbox-router-extproc): reject ports outside [1, 65535]
Port validation only ruled out the 0 sentinel that readHeaders sets on non-numeric input — and conveniently `r.port < 1` already covered negatives — but values above 65535 (e.g. "99999") were accepted and forwarded into x-envoy-original-dst-host as "10.0.0.1:99999". Envoy rejects that downstream with a less actionable error than our 400. Tighten the check to `r.port < 1 || r.port > 65535` and clean up the redundant `|| r.port == 0`. Convert TestHandle_InvalidPortRejected into a table covering non-numeric, empty, zero, negative, just-over, big, and max-int32; add TestHandle_PortBoundariesAccepted to lock in that 1 and 65535 still make it through.
1 parent f51b180 commit 6437679

2 files changed

Lines changed: 70 additions & 10 deletions

File tree

sandbox-router/internal/extproc/server.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,13 @@ func (s *Server) onRequestHeaders(_ context.Context, hdrs *extprocv3.HttpHeaders
211211
if !validNamespace(r.namespace) {
212212
return immediate(400, `{"detail":"Invalid namespace format."}`)
213213
}
214-
if r.port < 1 || r.port == 0 {
214+
// TCP port range is [1, 65535]. Reject anything outside it (and the
215+
// 0 sentinel readHeaders sets on non-numeric input) before we hand
216+
// the value to net.JoinHostPort — an out-of-range port would
217+
// produce a syntactically valid but semantically junk
218+
// x-envoy-original-dst-host (e.g. "10.0.0.1:99999") that Envoy
219+
// would reject downstream with a less actionable error.
220+
if r.port < 1 || r.port > 65535 {
215221
return immediate(400, `{"detail":"Invalid port format."}`)
216222
}
217223

sandbox-router/internal/extproc/server_test.go

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -199,17 +199,71 @@ func TestHandle_InvalidNamespaceRejected(t *testing.T) {
199199
}
200200

201201
func TestHandle_InvalidPortRejected(t *testing.T) {
202+
// Port must fall in [1, 65535]. Anything else gets rejected with a
203+
// 400 immediate before we hand the value to net.JoinHostPort, so a
204+
// bogus value can't ride along into x-envoy-original-dst-host and
205+
// surface as a less actionable Envoy error.
206+
cases := []struct {
207+
name string
208+
port string
209+
}{
210+
{"non-numeric", "abc"},
211+
{"empty-after-trim", " "},
212+
{"zero", "0"},
213+
{"negative", "-1"},
214+
{"way negative", "-99999"},
215+
{"just over 65535", "65536"},
216+
{"big number", "100000"},
217+
{"max int32", "2147483647"},
218+
}
202219
s, _ := newServer(t)
203-
resp := s.handle(context.Background(), reqHeaders(map[string]string{
204-
HeaderSandboxID: "alpha",
205-
HeaderSandboxPort: "abc",
206-
}))
207-
ir := resp.GetImmediateResponse()
208-
if ir == nil || ir.Status.Code != 400 {
209-
t.Fatalf("expected 400 immediate; got: %+v", resp)
220+
for _, tc := range cases {
221+
t.Run(tc.name, func(t *testing.T) {
222+
resp := s.handle(context.Background(), reqHeaders(map[string]string{
223+
HeaderSandboxID: "alpha",
224+
HeaderSandboxPort: tc.port,
225+
}))
226+
ir := resp.GetImmediateResponse()
227+
if ir == nil || ir.Status.Code != 400 {
228+
t.Fatalf("port=%q: expected 400 immediate; got: %+v", tc.port, resp)
229+
}
230+
if !strings.Contains(string(ir.Body), "port") {
231+
t.Errorf("port=%q: body should mention port: %s", tc.port, ir.Body)
232+
}
233+
})
210234
}
211-
if !strings.Contains(string(ir.Body), "port") {
212-
t.Errorf("body should mention port: %s", ir.Body)
235+
}
236+
237+
func TestHandle_PortBoundariesAccepted(t *testing.T) {
238+
// The smallest and largest legal TCP ports both make it through.
239+
s, stub := newServer(t)
240+
uid := types.UID("boundary-uid")
241+
stub[uid] = cache.Entry{PodIP: "10.0.0.1"}
242+
for _, port := range []string{"1", "65535"} {
243+
t.Run(port, func(t *testing.T) {
244+
resp := s.handle(context.Background(), reqHeaders(map[string]string{
245+
HeaderSandboxID: "alpha",
246+
HeaderSandboxUID: string(uid),
247+
HeaderSandboxPort: port,
248+
}))
249+
if ir := resp.GetImmediateResponse(); ir != nil {
250+
t.Fatalf("port %s: unexpected immediate %d / %s", port, ir.Status.Code, ir.Body)
251+
}
252+
rh := resp.GetRequestHeaders()
253+
if rh == nil {
254+
t.Fatalf("port %s: expected RequestHeaders mutation; got %+v", port, resp)
255+
}
256+
want := "10.0.0.1:" + port
257+
got := ""
258+
for _, h := range rh.Response.HeaderMutation.SetHeaders {
259+
if h.Header.Key == HeaderOriginalDstHost {
260+
got = string(h.Header.RawValue)
261+
}
262+
}
263+
if got != want {
264+
t.Fatalf("port %s: dst host got %q want %q", port, got, want)
265+
}
266+
})
213267
}
214268
}
215269

0 commit comments

Comments
 (0)