From f122c5093b9e00206d6df7cc106bb8442cb99a7f Mon Sep 17 00:00:00 2001 From: Sandeep Karambelkar Date: Fri, 14 Mar 2025 23:04:25 +0530 Subject: [PATCH] Patch keda to fix CVE-2022-3162 (#12848) Co-authored-by: jslobodzian (cherry picked from commit 8cfde50a60bd44d0cb5bd66336193ce2b59d28cd) --- SPECS/keda/CVE-2022-3162.patch | 320 +++++++++++++++++++++++++++++++++ SPECS/keda/keda.spec | 6 +- 2 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 SPECS/keda/CVE-2022-3162.patch diff --git a/SPECS/keda/CVE-2022-3162.patch b/SPECS/keda/CVE-2022-3162.patch new file mode 100644 index 00000000000..4912ea1163a --- /dev/null +++ b/SPECS/keda/CVE-2022-3162.patch @@ -0,0 +1,320 @@ +From 45e0bb4829c9c4cfb4e9b968f98b992659342b7e Mon Sep 17 00:00:00 2001 +From: Tim Allclair +Date: Mon, 10 Oct 2022 18:15:22 -0700 +Subject: [PATCH] Validate etcd paths + +--- + .../apiserver/pkg/storage/etcd3/store.go | 127 +++++++++++++----- + 1 file changed, 92 insertions(+), 35 deletions(-) + +diff --git a/vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go b/vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go +index 67ae9f5..6b3b808 100644 +--- a/vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go ++++ b/vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go +@@ -89,6 +89,14 @@ func New(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object, + + func newStore(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object, prefix string, transformer value.Transformer, pagingEnabled bool, leaseManagerConfig LeaseManagerConfig) *store { + versioner := APIObjectVersioner{} ++ // for compatibility with etcd2 impl. ++ // no-op for default prefix of '/registry'. ++ // keeps compatibility with etcd2 impl for custom prefixes that don't start with '/' ++ pathPrefix := path.Join("/", prefix) ++ if !strings.HasSuffix(pathPrefix, "/") { ++ // Ensure the pathPrefix ends in "/" here to simplify key concatenation later. ++ pathPrefix += "/" ++ } + result := &store{ + client: c, + codec: codec, +@@ -98,9 +106,9 @@ func newStore(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Ob + // for compatibility with etcd2 impl. + // no-op for default prefix of '/registry'. + // keeps compatibility with etcd2 impl for custom prefixes that don't start with '/' +- pathPrefix: path.Join("/", prefix), +- watcher: newWatcher(c, codec, newFunc, versioner, transformer), +- leaseManager: newDefaultLeaseManager(c, leaseManagerConfig), ++ pathPrefix: pathPrefix, ++ watcher: newWatcher(c, codec, newFunc, versioner, transformer), ++ leaseManager: newDefaultLeaseManager(c, leaseManagerConfig), + } + return result + } +@@ -112,9 +120,12 @@ func (s *store) Versioner() storage.Versioner { + + // Get implements storage.Interface.Get. + func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, out runtime.Object) error { +- key = path.Join(s.pathPrefix, key) ++ preparedKey, err := s.prepareKey(key) ++ if err != nil { ++ return err ++ } + startTime := time.Now() +- getResp, err := s.client.KV.Get(ctx, key) ++ getResp, err := s.client.KV.Get(ctx, preparedKey) + metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime) + if err != nil { + return err +@@ -127,11 +138,11 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou + if opts.IgnoreNotFound { + return runtime.SetZeroValue(out) + } +- return storage.NewKeyNotFoundError(key, 0) ++ return storage.NewKeyNotFoundError(preparedKey, 0) + } + kv := getResp.Kvs[0] + +- data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(key)) ++ data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(preparedKey)) + if err != nil { + return storage.NewInternalError(err.Error()) + } +@@ -141,6 +152,10 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou + + // Create implements storage.Interface.Create. + func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error { ++ preparedKey, err := s.prepareKey(key) ++ if err != nil { ++ return err ++ } + if version, err := s.versioner.ObjectResourceVersion(obj); err == nil && version != 0 { + return errors.New("resourceVersion should not be set on objects to be created") + } +@@ -151,30 +166,29 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object, + if err != nil { + return err + } +- key = path.Join(s.pathPrefix, key) + + opts, err := s.ttlOpts(ctx, int64(ttl)) + if err != nil { + return err + } + +- newData, err := s.transformer.TransformToStorage(data, authenticatedDataString(key)) ++ newData, err := s.transformer.TransformToStorage(data, authenticatedDataString(preparedKey)) + if err != nil { + return storage.NewInternalError(err.Error()) + } + + startTime := time.Now() + txnResp, err := s.client.KV.Txn(ctx).If( +- notFound(key), ++ notFound(preparedKey), + ).Then( +- clientv3.OpPut(key, string(newData), opts...), ++ clientv3.OpPut(preparedKey, string(newData), opts...), + ).Commit() + metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime) + if err != nil { + return err + } + if !txnResp.Succeeded { +- return storage.NewKeyExistsError(key, 0) ++ return storage.NewKeyExistsError(preparedKey, 0) + } + + if out != nil { +@@ -186,12 +200,15 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object, + + // Delete implements storage.Interface.Delete. + func (s *store) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc) error { ++ preparedKey, err := s.prepareKey(key) ++ if err != nil { ++ return err ++ } + v, err := conversion.EnforcePtr(out) + if err != nil { + return fmt.Errorf("unable to convert output object to pointer: %v", err) + } +- key = path.Join(s.pathPrefix, key) +- return s.conditionalDelete(ctx, key, out, v, preconditions, validateDeletion) ++ return s.conditionalDelete(ctx, preparedKey, out, v, preconditions, validateDeletion) + } + + func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.Object, v reflect.Value, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc) error { +@@ -239,6 +256,10 @@ func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.O + func (s *store) GuaranteedUpdate( + ctx context.Context, key string, out runtime.Object, ignoreNotFound bool, + preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, suggestion runtime.Object) error { ++ preparedKey, err := s.prepareKey(key) ++ if err != nil { ++ return err ++ } + trace := utiltrace.New("GuaranteedUpdate etcd3", utiltrace.Field{"type", getTypeName(out)}) + defer trace.LogIfLong(500 * time.Millisecond) + +@@ -246,16 +267,15 @@ func (s *store) GuaranteedUpdate( + if err != nil { + return fmt.Errorf("unable to convert output object to pointer: %v", err) + } +- key = path.Join(s.pathPrefix, key) + + getCurrentState := func() (*objState, error) { + startTime := time.Now() +- getResp, err := s.client.KV.Get(ctx, key) ++ getResp, err := s.client.KV.Get(ctx, preparedKey) + metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime) + if err != nil { + return nil, err + } +- return s.getState(getResp, key, v, ignoreNotFound) ++ return s.getState(getResp, preparedKey, v, ignoreNotFound) + } + + var origState *objState +@@ -274,9 +294,9 @@ func (s *store) GuaranteedUpdate( + } + trace.Step("initial value restored") + +- transformContext := authenticatedDataString(key) ++ transformContext := authenticatedDataString(preparedKey) + for { +- if err := preconditions.Check(key, origState.obj); err != nil { ++ if err := preconditions.Check(preparedKey, origState.obj); err != nil { + // If our data is already up to date, return the error + if !mustCheckData { + return err +@@ -349,11 +369,11 @@ func (s *store) GuaranteedUpdate( + + startTime := time.Now() + txnResp, err := s.client.KV.Txn(ctx).If( +- clientv3.Compare(clientv3.ModRevision(key), "=", origState.rev), ++ clientv3.Compare(clientv3.ModRevision(preparedKey), "=", origState.rev), + ).Then( +- clientv3.OpPut(key, string(newData), opts...), ++ clientv3.OpPut(preparedKey, string(newData), opts...), + ).Else( +- clientv3.OpGet(key), ++ clientv3.OpGet(preparedKey), + ).Commit() + metrics.RecordEtcdRequestLatency("update", getTypeName(out), startTime) + if err != nil { +@@ -362,8 +382,8 @@ func (s *store) GuaranteedUpdate( + trace.Step("Transaction committed") + if !txnResp.Succeeded { + getResp := (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange()) +- klog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", key) +- origState, err = s.getState(getResp, key, v, ignoreNotFound) ++ klog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", preparedKey) ++ origState, err = s.getState(getResp, preparedKey, v, ignoreNotFound) + if err != nil { + return err + } +@@ -400,7 +420,10 @@ func (s *store) GetToList(ctx context.Context, key string, listOpts storage.List + + newItemFunc := getNewItemFunc(listObj, v) + +- key = path.Join(s.pathPrefix, key) ++ preparedKey, err := s.prepareKey(key) ++ if err != nil { ++ return err ++ } + startTime := time.Now() + var opts []clientv3.OpOption + if len(resourceVersion) > 0 && match == metav1.ResourceVersionMatchExact { +@@ -411,7 +434,7 @@ func (s *store) GetToList(ctx context.Context, key string, listOpts storage.List + opts = append(opts, clientv3.WithRev(int64(rv))) + } + +- getResp, err := s.client.KV.Get(ctx, key, opts...) ++ getResp, err := s.client.KV.Get(ctx, preparedKey, opts...) + metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime) + if err != nil { + return err +@@ -421,7 +444,7 @@ func (s *store) GetToList(ctx context.Context, key string, listOpts storage.List + } + + if len(getResp.Kvs) > 0 { +- data, _, err := s.transformer.TransformFromStorage(getResp.Kvs[0].Value, authenticatedDataString(key)) ++ data, _, err := s.transformer.TransformFromStorage(getResp.Kvs[0].Value, authenticatedDataString(preparedKey)) + if err != nil { + return storage.NewInternalError(err.Error()) + } +@@ -451,18 +474,21 @@ func getNewItemFunc(listObj runtime.Object, v reflect.Value) func() runtime.Obje + } + + func (s *store) Count(key string) (int64, error) { +- key = path.Join(s.pathPrefix, key) ++ preparedKey, err := s.prepareKey(key) ++ if err != nil { ++ return 0, err ++ } + + // We need to make sure the key ended with "/" so that we only get children "directories". + // e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three, + // while with prefix "/a/" will return only "/a/b" which is the correct answer. +- if !strings.HasSuffix(key, "/") { +- key += "/" ++ if !strings.HasSuffix(preparedKey, "/") { ++ preparedKey += "/" + } + + startTime := time.Now() +- getResp, err := s.client.KV.Get(context.Background(), key, clientv3.WithRange(clientv3.GetPrefixRangeEnd(key)), clientv3.WithCountOnly()) +- metrics.RecordEtcdRequestLatency("listWithCount", key, startTime) ++ getResp, err := s.client.KV.Get(context.Background(), preparedKey, clientv3.WithRange(clientv3.GetPrefixRangeEnd(preparedKey)), clientv3.WithCountOnly()) ++ metrics.RecordEtcdRequestLatency("listWithCount", preparedKey, startTime) + if err != nil { + return 0, err + } +@@ -551,7 +577,11 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions, + } + + if s.pathPrefix != "" { +- key = path.Join(s.pathPrefix, key) ++ preparedKey, err := s.prepareKey(key) ++ if err != nil { ++ return err ++ } ++ key = preparedKey + } + // We need to make sure the key ended with "/" so that we only get children "directories". + // e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three, +@@ -783,8 +813,11 @@ func (s *store) watch(ctx context.Context, key string, opts storage.ListOptions, + if err != nil { + return nil, err + } +- key = path.Join(s.pathPrefix, key) +- return s.watcher.Watch(ctx, key, int64(rev), recursive, opts.ProgressNotify, opts.Predicate) ++ preparedKey, err := s.prepareKey(key) ++ if err != nil { ++ return nil, err ++ } ++ return s.watcher.Watch(ctx, preparedKey, int64(rev), recursive, opts.ProgressNotify, opts.Predicate) + } + + func (s *store) getState(getResp *clientv3.GetResponse, key string, v reflect.Value, ignoreNotFound bool) (*objState, error) { +@@ -896,6 +929,30 @@ func (s *store) validateMinimumResourceVersion(minimumResourceVersion string, ac + return nil + } + ++func (s *store) prepareKey(key string) (string, error) { ++ if key == ".." || ++ strings.HasPrefix(key, "../") || ++ strings.HasSuffix(key, "/..") || ++ strings.Contains(key, "/../") { ++ return "", fmt.Errorf("invalid key: %q", key) ++ } ++ if key == "." || ++ strings.HasPrefix(key, "./") || ++ strings.HasSuffix(key, "/.") || ++ strings.Contains(key, "/./") { ++ return "", fmt.Errorf("invalid key: %q", key) ++ } ++ if key == "" || key == "/" { ++ return "", fmt.Errorf("empty key: %q", key) ++ } ++ // We ensured that pathPrefix ends in '/' in construction, so skip any leading '/' in the key now. ++ startIndex := 0 ++ if key[0] == '/' { ++ startIndex = 1 ++ } ++ return s.pathPrefix + key[startIndex:], nil ++} ++ + // decode decodes value of bytes into object. It will also set the object resource version to rev. + // On success, objPtr would be set to the object. + func decode(codec runtime.Codec, versioner storage.Versioner, value []byte, objPtr runtime.Object, rev int64) error { +-- +2.40.4 + diff --git a/SPECS/keda/keda.spec b/SPECS/keda/keda.spec index fff0007bc67..358ef6be6b2 100644 --- a/SPECS/keda/keda.spec +++ b/SPECS/keda/keda.spec @@ -1,7 +1,7 @@ Summary: Kubernetes-based Event Driven Autoscaling Name: keda Version: 2.4.0 -Release: 27%{?dist} +Release: 28%{?dist} License: ASL 2.0 Vendor: Microsoft Corporation Distribution: Mariner @@ -36,6 +36,7 @@ Patch4: CVE-2024-6104.patch Patch5: CVE-2024-45338.patch Patch6: CVE-2024-28180.patch Patch7: CVE-2025-27144.patch +Patch8: CVE-2022-3162.patch BuildRequires: golang @@ -71,6 +72,9 @@ cp ./bin/keda-adapter %{buildroot}%{_bindir} %{_bindir}/%{name}-adapter %changelog +* Thu Mar 06 2025 Sandeep Karambelkar - 2.4.0-28 +- Fix CVE-2022-3162 with upstream patch + * Fri Feb 28 2025 Kanishk Bansal - 2.4.0-27 - Fix CVE-2025-27144 with an upstream patch