Skip to content

Commit 75ddd43

Browse files
[AUTO-CHERRYPICK] [Medium] Patch rook for CVE-2022-3162 - branch main (#12955)
Co-authored-by: Kevin Lockwood <57274670+kevin-b-lockwood@users.noreply.github.com>
1 parent 477d3f9 commit 75ddd43

2 files changed

Lines changed: 387 additions & 1 deletion

File tree

SPECS/rook/CVE-2022-3162.patch

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
From e7b77750506ad3c40a47d554c61e8b04923f1094 Mon Sep 17 00:00:00 2001
2+
From: Kevin Lockwood <v-klockwood@microsoft.com>
3+
Date: Fri, 28 Feb 2025 14:26:41 -0800
4+
Subject: [PATCH] [Medium] Patch rook for CVE-2022-3162
5+
6+
Link: https://github.com/kubernetes/kubernetes/pull/113687/commits/54269f2b186ea2a37bb4e9a22e797fdc6ebdcb37.patch
7+
---
8+
.../apiserver/pkg/storage/etcd3/store.go | 146 ++++++++++++------
9+
1 file changed, 100 insertions(+), 46 deletions(-)
10+
11+
diff --git a/vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go b/vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go
12+
index 0cff6b3..f725465 100644
13+
--- a/vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go
14+
+++ b/vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go
15+
@@ -89,18 +89,23 @@ func New(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object,
16+
17+
func newStore(c *clientv3.Client, newFunc func() runtime.Object, pagingEnabled bool, codec runtime.Codec, prefix string, transformer value.Transformer) *store {
18+
versioner := APIObjectVersioner{}
19+
+ // for compatibility with etcd2 impl.
20+
+ // no-op for default prefix of '/registry'.
21+
+ // keeps compatibility with etcd2 impl for custom prefixes that don't start with '/'
22+
+ pathPrefix := path.Join("/", prefix)
23+
+ if !strings.HasSuffix(pathPrefix, "/") {
24+
+ // Ensure the pathPrefix ends in "/" here to simplify key concatenation later.
25+
+ pathPrefix += "/"
26+
+ }
27+
result := &store{
28+
client: c,
29+
codec: codec,
30+
versioner: versioner,
31+
transformer: transformer,
32+
pagingEnabled: pagingEnabled,
33+
- // for compatibility with etcd2 impl.
34+
- // no-op for default prefix of '/registry'.
35+
- // keeps compatibility with etcd2 impl for custom prefixes that don't start with '/'
36+
- pathPrefix: path.Join("/", prefix),
37+
- watcher: newWatcher(c, codec, newFunc, versioner, transformer),
38+
- leaseManager: newDefaultLeaseManager(c),
39+
+ pathPrefix: pathPrefix,
40+
+ watcher: newWatcher(c, codec, newFunc, versioner, transformer),
41+
+ leaseManager: newDefaultLeaseManager(c),
42+
}
43+
return result
44+
}
45+
@@ -112,9 +117,12 @@ func (s *store) Versioner() storage.Versioner {
46+
47+
// Get implements storage.Interface.Get.
48+
func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, out runtime.Object) error {
49+
- key = path.Join(s.pathPrefix, key)
50+
+ preparedKey, err := s.prepareKey(key)
51+
+ if err != nil {
52+
+ return err
53+
+ }
54+
startTime := time.Now()
55+
- getResp, err := s.client.KV.Get(ctx, key)
56+
+ getResp, err := s.client.KV.Get(ctx, preparedKey)
57+
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
58+
if err != nil {
59+
return err
60+
@@ -127,11 +135,11 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou
61+
if opts.IgnoreNotFound {
62+
return runtime.SetZeroValue(out)
63+
}
64+
- return storage.NewKeyNotFoundError(key, 0)
65+
+ return storage.NewKeyNotFoundError(preparedKey, 0)
66+
}
67+
kv := getResp.Kvs[0]
68+
69+
- data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(key))
70+
+ data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(preparedKey))
71+
if err != nil {
72+
return storage.NewInternalError(err.Error())
73+
}
74+
@@ -141,6 +149,11 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou
75+
76+
// Create implements storage.Interface.Create.
77+
func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error {
78+
+ preparedKey, err := s.prepareKey(key)
79+
+ if err != nil {
80+
+ return err
81+
+ }
82+
+
83+
if version, err := s.versioner.ObjectResourceVersion(obj); err == nil && version != 0 {
84+
return errors.New("resourceVersion should not be set on objects to be created")
85+
}
86+
@@ -151,30 +164,29 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
87+
if err != nil {
88+
return err
89+
}
90+
- key = path.Join(s.pathPrefix, key)
91+
92+
opts, err := s.ttlOpts(ctx, int64(ttl))
93+
if err != nil {
94+
return err
95+
}
96+
97+
- newData, err := s.transformer.TransformToStorage(data, authenticatedDataString(key))
98+
+ newData, err := s.transformer.TransformToStorage(data, authenticatedDataString(preparedKey))
99+
if err != nil {
100+
return storage.NewInternalError(err.Error())
101+
}
102+
103+
startTime := time.Now()
104+
txnResp, err := s.client.KV.Txn(ctx).If(
105+
- notFound(key),
106+
+ notFound(preparedKey),
107+
).Then(
108+
- clientv3.OpPut(key, string(newData), opts...),
109+
+ clientv3.OpPut(preparedKey, string(newData), opts...),
110+
).Commit()
111+
metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime)
112+
if err != nil {
113+
return err
114+
}
115+
if !txnResp.Succeeded {
116+
- return storage.NewKeyExistsError(key, 0)
117+
+ return storage.NewKeyExistsError(preparedKey, 0)
118+
}
119+
120+
if out != nil {
121+
@@ -186,12 +198,15 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
122+
123+
// Delete implements storage.Interface.Delete.
124+
func (s *store) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc) error {
125+
+ preparedKey, err := s.prepareKey(key)
126+
+ if err != nil {
127+
+ return err
128+
+ }
129+
v, err := conversion.EnforcePtr(out)
130+
if err != nil {
131+
return fmt.Errorf("unable to convert output object to pointer: %v", err)
132+
}
133+
- key = path.Join(s.pathPrefix, key)
134+
- return s.conditionalDelete(ctx, key, out, v, preconditions, validateDeletion)
135+
+ return s.conditionalDelete(ctx, preparedKey, out, v, preconditions, validateDeletion)
136+
}
137+
138+
func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.Object, v reflect.Value, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc) error {
139+
@@ -239,6 +254,10 @@ func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.O
140+
func (s *store) GuaranteedUpdate(
141+
ctx context.Context, key string, out runtime.Object, ignoreNotFound bool,
142+
preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, suggestion runtime.Object) error {
143+
+ preparedKey, err := s.prepareKey(key)
144+
+ if err != nil {
145+
+ return err
146+
+ }
147+
trace := utiltrace.New("GuaranteedUpdate etcd3", utiltrace.Field{"type", getTypeName(out)})
148+
defer trace.LogIfLong(500 * time.Millisecond)
149+
150+
@@ -246,16 +265,15 @@ func (s *store) GuaranteedUpdate(
151+
if err != nil {
152+
return fmt.Errorf("unable to convert output object to pointer: %v", err)
153+
}
154+
- key = path.Join(s.pathPrefix, key)
155+
156+
getCurrentState := func() (*objState, error) {
157+
startTime := time.Now()
158+
- getResp, err := s.client.KV.Get(ctx, key)
159+
+ getResp, err := s.client.KV.Get(ctx, preparedKey)
160+
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
161+
if err != nil {
162+
return nil, err
163+
}
164+
- return s.getState(getResp, key, v, ignoreNotFound)
165+
+ return s.getState(getResp, preparedKey, v, ignoreNotFound)
166+
}
167+
168+
var origState *objState
169+
@@ -274,9 +292,9 @@ func (s *store) GuaranteedUpdate(
170+
}
171+
trace.Step("initial value restored")
172+
173+
- transformContext := authenticatedDataString(key)
174+
+ transformContext := authenticatedDataString(preparedKey)
175+
for {
176+
- if err := preconditions.Check(key, origState.obj); err != nil {
177+
+ if err := preconditions.Check(preparedKey, origState.obj); err != nil {
178+
// If our data is already up to date, return the error
179+
if !mustCheckData {
180+
return err
181+
@@ -349,11 +367,11 @@ func (s *store) GuaranteedUpdate(
182+
183+
startTime := time.Now()
184+
txnResp, err := s.client.KV.Txn(ctx).If(
185+
- clientv3.Compare(clientv3.ModRevision(key), "=", origState.rev),
186+
+ clientv3.Compare(clientv3.ModRevision(preparedKey), "=", origState.rev),
187+
).Then(
188+
- clientv3.OpPut(key, string(newData), opts...),
189+
+ clientv3.OpPut(preparedKey, string(newData), opts...),
190+
).Else(
191+
- clientv3.OpGet(key),
192+
+ clientv3.OpGet(preparedKey),
193+
).Commit()
194+
metrics.RecordEtcdRequestLatency("update", getTypeName(out), startTime)
195+
if err != nil {
196+
@@ -362,8 +380,8 @@ func (s *store) GuaranteedUpdate(
197+
trace.Step("Transaction committed")
198+
if !txnResp.Succeeded {
199+
getResp := (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange())
200+
- klog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", key)
201+
- origState, err = s.getState(getResp, key, v, ignoreNotFound)
202+
+ klog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", preparedKey)
203+
+ origState, err = s.getState(getResp, preparedKey, v, ignoreNotFound)
204+
if err != nil {
205+
return err
206+
}
207+
@@ -379,6 +397,11 @@ func (s *store) GuaranteedUpdate(
208+
209+
// GetToList implements storage.Interface.GetToList.
210+
func (s *store) GetToList(ctx context.Context, key string, listOpts storage.ListOptions, listObj runtime.Object) error {
211+
+ preparedKey, err := s.prepareKey(key)
212+
+ if err != nil {
213+
+ return err
214+
+ }
215+
+
216+
resourceVersion := listOpts.ResourceVersion
217+
match := listOpts.ResourceVersionMatch
218+
pred := listOpts.Predicate
219+
@@ -400,7 +423,6 @@ func (s *store) GetToList(ctx context.Context, key string, listOpts storage.List
220+
221+
newItemFunc := getNewItemFunc(listObj, v)
222+
223+
- key = path.Join(s.pathPrefix, key)
224+
startTime := time.Now()
225+
var opts []clientv3.OpOption
226+
if len(resourceVersion) > 0 && match == metav1.ResourceVersionMatchExact {
227+
@@ -411,7 +433,7 @@ func (s *store) GetToList(ctx context.Context, key string, listOpts storage.List
228+
opts = append(opts, clientv3.WithRev(int64(rv)))
229+
}
230+
231+
- getResp, err := s.client.KV.Get(ctx, key, opts...)
232+
+ getResp, err := s.client.KV.Get(ctx, preparedKey, opts...)
233+
metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime)
234+
if err != nil {
235+
return err
236+
@@ -421,7 +443,7 @@ func (s *store) GetToList(ctx context.Context, key string, listOpts storage.List
237+
}
238+
239+
if len(getResp.Kvs) > 0 {
240+
- data, _, err := s.transformer.TransformFromStorage(getResp.Kvs[0].Value, authenticatedDataString(key))
241+
+ data, _, err := s.transformer.TransformFromStorage(getResp.Kvs[0].Value, authenticatedDataString(preparedKey))
242+
if err != nil {
243+
return storage.NewInternalError(err.Error())
244+
}
245+
@@ -451,18 +473,21 @@ func getNewItemFunc(listObj runtime.Object, v reflect.Value) func() runtime.Obje
246+
}
247+
248+
func (s *store) Count(key string) (int64, error) {
249+
- key = path.Join(s.pathPrefix, key)
250+
+ preparedKey, err := s.prepareKey(key)
251+
+ if err != nil {
252+
+ return 0, err
253+
+ }
254+
255+
// We need to make sure the key ended with "/" so that we only get children "directories".
256+
// e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three,
257+
// while with prefix "/a/" will return only "/a/b" which is the correct answer.
258+
- if !strings.HasSuffix(key, "/") {
259+
- key += "/"
260+
+ if !strings.HasSuffix(preparedKey, "/") {
261+
+ preparedKey += "/"
262+
}
263+
264+
startTime := time.Now()
265+
- getResp, err := s.client.KV.Get(context.Background(), key, clientv3.WithRange(clientv3.GetPrefixRangeEnd(key)), clientv3.WithCountOnly())
266+
- metrics.RecordEtcdRequestLatency("listWithCount", key, startTime)
267+
+ getResp, err := s.client.KV.Get(context.Background(), preparedKey, clientv3.WithRange(clientv3.GetPrefixRangeEnd(preparedKey)), clientv3.WithCountOnly())
268+
+ metrics.RecordEtcdRequestLatency("listWithCount", preparedKey, startTime)
269+
if err != nil {
270+
return 0, err
271+
}
272+
@@ -531,6 +556,11 @@ func encodeContinue(key, keyPrefix string, resourceVersion int64) (string, error
273+
274+
// List implements storage.Interface.List.
275+
func (s *store) List(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error {
276+
+ preparedKey, err := s.prepareKey(key)
277+
+ if err != nil {
278+
+ return err
279+
+ }
280+
+
281+
resourceVersion := opts.ResourceVersion
282+
match := opts.ResourceVersionMatch
283+
pred := opts.Predicate
284+
@@ -550,16 +580,13 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
285+
return fmt.Errorf("need ptr to slice: %v", err)
286+
}
287+
288+
- if s.pathPrefix != "" {
289+
- key = path.Join(s.pathPrefix, key)
290+
- }
291+
// We need to make sure the key ended with "/" so that we only get children "directories".
292+
// e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three,
293+
// while with prefix "/a/" will return only "/a/b" which is the correct answer.
294+
- if !strings.HasSuffix(key, "/") {
295+
- key += "/"
296+
+ if !strings.HasSuffix(preparedKey, "/") {
297+
+ preparedKey += "/"
298+
}
299+
- keyPrefix := key
300+
+ keyPrefix := preparedKey
301+
302+
// set the appropriate clientv3 options to filter the returned data set
303+
var paging bool
304+
@@ -595,7 +622,7 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
305+
306+
rangeEnd := clientv3.GetPrefixRangeEnd(keyPrefix)
307+
options = append(options, clientv3.WithRange(rangeEnd))
308+
- key = continueKey
309+
+ preparedKey = continueKey
310+
311+
// If continueRV > 0, the LIST request needs a specific resource version.
312+
// continueRV==0 is invalid.
313+
@@ -652,7 +679,7 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
314+
var getResp *clientv3.GetResponse
315+
for {
316+
startTime := time.Now()
317+
- getResp, err = s.client.KV.Get(ctx, key, options...)
318+
+ getResp, err = s.client.KV.Get(ctx, preparedKey, options...)
319+
metrics.RecordEtcdRequestLatency("list", getTypeName(listPtr), startTime)
320+
if err != nil {
321+
return interpretListError(err, len(pred.Continue) > 0, continueKey, keyPrefix)
322+
@@ -705,7 +732,7 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
323+
if int64(v.Len()) >= pred.Limit {
324+
break
325+
}
326+
- key = string(lastKey) + "\x00"
327+
+ preparedKey = string(lastKey) + "\x00"
328+
if withRev == 0 {
329+
withRev = returnedRV
330+
options = append(options, clientv3.WithRev(withRev))
331+
@@ -779,12 +806,15 @@ func (s *store) WatchList(ctx context.Context, key string, opts storage.ListOpti
332+
}
333+
334+
func (s *store) watch(ctx context.Context, key string, opts storage.ListOptions, recursive bool) (watch.Interface, error) {
335+
+ preparedKey, err := s.prepareKey(key)
336+
+ if err != nil {
337+
+ return nil, err
338+
+ }
339+
rev, err := s.versioner.ParseResourceVersion(opts.ResourceVersion)
340+
if err != nil {
341+
return nil, err
342+
}
343+
- key = path.Join(s.pathPrefix, key)
344+
- return s.watcher.Watch(ctx, key, int64(rev), recursive, opts.ProgressNotify, opts.Predicate)
345+
+ return s.watcher.Watch(ctx, preparedKey, int64(rev), recursive, opts.ProgressNotify, opts.Predicate)
346+
}
347+
348+
func (s *store) getState(getResp *clientv3.GetResponse, key string, v reflect.Value, ignoreNotFound bool) (*objState, error) {
349+
@@ -896,6 +926,30 @@ func (s *store) validateMinimumResourceVersion(minimumResourceVersion string, ac
350+
return nil
351+
}
352+
353+
+func (s *store) prepareKey(key string) (string, error) {
354+
+ if key == ".." ||
355+
+ strings.HasPrefix(key, "../") ||
356+
+ strings.HasSuffix(key, "/..") ||
357+
+ strings.Contains(key, "/../") {
358+
+ return "", fmt.Errorf("invalid key: %q", key)
359+
+ }
360+
+ if key == "." ||
361+
+ strings.HasPrefix(key, "./") ||
362+
+ strings.HasSuffix(key, "/.") ||
363+
+ strings.Contains(key, "/./") {
364+
+ return "", fmt.Errorf("invalid key: %q", key)
365+
+ }
366+
+ if key == "" || key == "/" {
367+
+ return "", fmt.Errorf("empty key: %q", key)
368+
+ }
369+
+ // We ensured that pathPrefix ends in '/' in construction, so skip any leading '/' in the key now.
370+
+ startIndex := 0
371+
+ if key[0] == '/' {
372+
+ startIndex = 1
373+
+ }
374+
+ return s.pathPrefix + key[startIndex:], nil
375+
+}
376+
+
377+
// decode decodes value of bytes into object. It will also set the object resource version to rev.
378+
// On success, objPtr would be set to the object.
379+
func decode(codec runtime.Codec, versioner storage.Versioner, value []byte, objPtr runtime.Object, rev int64) error {
380+
--
381+
2.34.1
382+

0 commit comments

Comments
 (0)