From 03771cfec2191f46bf25dca03b6270a0fa3a69d6 Mon Sep 17 00:00:00 2001 From: Marwan Atia Date: Thu, 30 Apr 2026 20:54:32 +0000 Subject: [PATCH 1/3] ext/lists: add max size check to genRange() to prevent OOM lists.range(N) allocates N elements with no upper bound. A large value like 2147483647 allocates ~16 GB and crashes the process. This is dangerous in Kubernetes ValidatingAdmissionPolicy where user-controlled input feeds into CEL expressions. Add maxRangeSize (10M) check and reject negative values. --- ext/lists.go | 11 ++++++++++- ext/lists_test.go | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/ext/lists.go b/ext/lists.go index 7a7a33c62..329a3b190 100644 --- a/ext/lists.go +++ b/ext/lists.go @@ -403,8 +403,17 @@ func (lib *listsLib) ProgramOptions() []cel.ProgramOption { return opts } +// maxRangeSize is the maximum number of elements lists.range() will allocate. +const maxRangeSize = 10_000_000 + func genRange(n types.Int) (ref.Val, error) { - var newList []ref.Val + if n < 0 { + return nil, fmt.Errorf("lists.range: size must be non-negative, got %d", n) + } + if n > maxRangeSize { + return nil, fmt.Errorf("lists.range: size %d exceeds maximum allowed (%d)", n, maxRangeSize) + } + newList := make([]ref.Val, 0, n) for i := types.Int(0); i < n; i++ { newList = append(newList, i) } diff --git a/ext/lists_test.go b/ext/lists_test.go index 42a2db6bf..086ae1a43 100644 --- a/ext/lists_test.go +++ b/ext/lists_test.go @@ -521,3 +521,26 @@ func testListsEnv(t *testing.T, opts ...cel.EnvOption) *cel.Env { } return env } + +func TestGenRangeMaxSize(t *testing.T) { + // Negative size should fail. + _, err := genRange(-1) + if err == nil { + t.Error("genRange(-1) should fail") + } + + // Small size should work. + val, err := genRange(10) + if err != nil { + t.Fatalf("genRange(10) failed: %v", err) + } + if val == nil { + t.Fatal("genRange(10) returned nil") + } + + // Over the limit should fail, not allocate. + _, err = genRange(maxRangeSize + 1) + if err == nil { + t.Error("genRange(maxRangeSize+1) should fail") + } +} From a1d200b4e28e9980cde42a0c2a80f34de45007e6 Mon Sep 17 00:00:00 2001 From: Marwan Atia Date: Thu, 30 Apr 2026 21:11:17 +0000 Subject: [PATCH 2/3] trigger CLA re-check From 1ea6b7ddf5a01cc1117a750ba3f90797806ac412 Mon Sep 17 00:00:00 2001 From: Marwan Atia Date: Tue, 5 May 2026 12:09:03 +0000 Subject: [PATCH 3/3] ext/lists: make maxRangeSize a functional option Move the hardcoded maxRangeSize constant into a ListsMaxRangeSize functional option on the Lists library, following the same pattern as ListsVersion. Default remains 10,000,000. Setting to zero disables the limit. --- ext/lists.go | 29 ++++++++++++++++++++--------- ext/lists_test.go | 17 +++++++++++++---- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/ext/lists.go b/ext/lists.go index 329a3b190..cbf7ce4e5 100644 --- a/ext/lists.go +++ b/ext/lists.go @@ -153,15 +153,18 @@ var comparableTypes = []*cel.Type{ // ].sortBy(e, e.score).map(e, e.name) // == ["bar", "foo", "baz"] func Lists(options ...ListsOption) cel.EnvOption { - l := &listsLib{version: math.MaxUint32} + l := &listsLib{version: math.MaxUint32, maxRangeSize: defaultMaxRangeSize} for _, o := range options { l = o(l) } return cel.Lib(l) } +const defaultMaxRangeSize = 10_000_000 + type listsLib struct { - version uint32 + version uint32 + maxRangeSize int64 } // LibraryName implements the SingletonLibrary interface method. @@ -188,6 +191,16 @@ func ListsVersion(version uint32) ListsOption { } } +// ListsMaxRangeSize sets the maximum number of elements lists.range() will +// allocate. If not set, the default is 10,000,000. Setting this to zero +// disables the limit (not recommended). +func ListsMaxRangeSize(size int64) ListsOption { + return func(lib *listsLib) *listsLib { + lib.maxRangeSize = size + return lib + } +} + // CompileOptions implements the Library interface method. func (lib listsLib) CompileOptions() []cel.EnvOption { listType := cel.ListType(cel.TypeParamType("T")) @@ -309,11 +322,12 @@ func (lib listsLib) CompileOptions() []cel.EnvOption { )..., )) + maxRange := lib.maxRangeSize opts = append(opts, cel.Function("lists.range", cel.Overload("lists_range", []*cel.Type{cel.IntType}, cel.ListType(cel.IntType), cel.UnaryBinding(func(n ref.Val) ref.Val { - result, err := genRange(n.(types.Int)) + result, err := genRange(n.(types.Int), maxRange) if err != nil { return types.WrapErr(err) } @@ -403,15 +417,12 @@ func (lib *listsLib) ProgramOptions() []cel.ProgramOption { return opts } -// maxRangeSize is the maximum number of elements lists.range() will allocate. -const maxRangeSize = 10_000_000 - -func genRange(n types.Int) (ref.Val, error) { +func genRange(n types.Int, maxSize int64) (ref.Val, error) { if n < 0 { return nil, fmt.Errorf("lists.range: size must be non-negative, got %d", n) } - if n > maxRangeSize { - return nil, fmt.Errorf("lists.range: size %d exceeds maximum allowed (%d)", n, maxRangeSize) + if maxSize > 0 && int64(n) > maxSize { + return nil, fmt.Errorf("lists.range: size %d exceeds maximum allowed (%d)", n, maxSize) } newList := make([]ref.Val, 0, n) for i := types.Int(0); i < n; i++ { diff --git a/ext/lists_test.go b/ext/lists_test.go index 086ae1a43..bc5b5a7e5 100644 --- a/ext/lists_test.go +++ b/ext/lists_test.go @@ -524,13 +524,13 @@ func testListsEnv(t *testing.T, opts ...cel.EnvOption) *cel.Env { func TestGenRangeMaxSize(t *testing.T) { // Negative size should fail. - _, err := genRange(-1) + _, err := genRange(-1, defaultMaxRangeSize) if err == nil { t.Error("genRange(-1) should fail") } // Small size should work. - val, err := genRange(10) + val, err := genRange(10, defaultMaxRangeSize) if err != nil { t.Fatalf("genRange(10) failed: %v", err) } @@ -539,8 +539,17 @@ func TestGenRangeMaxSize(t *testing.T) { } // Over the limit should fail, not allocate. - _, err = genRange(maxRangeSize + 1) + _, err = genRange(defaultMaxRangeSize+1, defaultMaxRangeSize) if err == nil { - t.Error("genRange(maxRangeSize+1) should fail") + t.Error("genRange(defaultMaxRangeSize+1) should fail") + } + + // Zero limit disables the check. + val, err = genRange(100, 0) + if err != nil { + t.Fatalf("genRange(100, 0) with disabled limit failed: %v", err) + } + if val == nil { + t.Fatal("genRange(100, 0) returned nil") } }