Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
320 changes: 320 additions & 0 deletions SPECS/keda/CVE-2022-3162.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
From 45e0bb4829c9c4cfb4e9b968f98b992659342b7e Mon Sep 17 00:00:00 2001
From: Tim Allclair <tallclair@google.com>
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

6 changes: 5 additions & 1 deletion SPECS/keda/keda.spec
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -71,6 +72,9 @@ cp ./bin/keda-adapter %{buildroot}%{_bindir}
%{_bindir}/%{name}-adapter

%changelog
* Thu Mar 06 2025 Sandeep Karambelkar <skarambelkar@microsoft.com> - 2.4.0-28
- Fix CVE-2022-3162 with upstream patch

* Fri Feb 28 2025 Kanishk Bansal <kanbansal@microsoft.com> - 2.4.0-27
- Fix CVE-2025-27144 with an upstream patch

Expand Down