From 50afbb8ea2dea105851ab37ea4ffaf1fc5ce3628 Mon Sep 17 00:00:00 2001 From: bwzhang Date: Tue, 23 Apr 2024 16:17:10 +0800 Subject: [PATCH] fix CVE-2023-32082 (cherry picked from commit 4c39e804f9dec9967da689cd7bef6aad76c81db7) --- 0008-fix-CVE-2023-32082.patch | 255 ++++++++++++++++++++++++++++++++++ etcd.spec | 9 +- 2 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 0008-fix-CVE-2023-32082.patch diff --git a/0008-fix-CVE-2023-32082.patch b/0008-fix-CVE-2023-32082.patch new file mode 100644 index 0000000..4bbca61 --- /dev/null +++ b/0008-fix-CVE-2023-32082.patch @@ -0,0 +1,255 @@ +From 99f3aac02a570b11bac83fcdf9b92501b25dce5c Mon Sep 17 00:00:00 2001 +From: bwzhang +Date: Tue, 23 Apr 2024 16:15:29 +0800 +Subject: [PATCH] fix CVE-2023-32082 + +--- + etcdserver/v3_server.go | 28 ++++++++++- + integration/v3_auth_test.go | 91 ++++++++++++++++++++++++++++++++-- + pkg/ioutil/pagewriter_test.go | 2 +- + tests/e2e/ctl_v3_auth_test.go | 49 ++++++++++++++++++ + tests/e2e/ctl_v3_lease_test.go | 8 +++ + 5 files changed, 171 insertions(+), 7 deletions(-) + +diff --git a/etcdserver/v3_server.go b/etcdserver/v3_server.go +index 1fa8e4e..dee5c20 100644 +--- a/etcdserver/v3_server.go ++++ b/etcdserver/v3_server.go +@@ -298,7 +298,33 @@ func (s *EtcdServer) LeaseRenew(ctx context.Context, id lease.LeaseID) (int64, e + return -1, ErrCanceled + } + +-func (s *EtcdServer) LeaseTimeToLive(ctx context.Context, r *pb.LeaseTimeToLiveRequest) (*pb.LeaseTimeToLiveResponse, error) { ++func (s *EtcdServer) checkLeaseTimeToLive(ctx context.Context, leaseID lease.LeaseID) (error, uint64) { ++ rev := s.AuthStore().Revision() ++ if !s.AuthStore().IsAuthEnabled() { ++ return nil, rev ++ } ++ authInfo, err := s.AuthInfoFromCtx(ctx) ++ if err != nil { ++ return err, rev ++ } ++ if authInfo == nil { ++ return auth.ErrUserEmpty, rev ++ } ++ ++ l := s.lessor.Lookup(leaseID) ++ if l != nil { ++ for _, key := range l.Keys() { ++ if err := s.AuthStore().IsRangePermitted(authInfo, []byte(key), []byte{}); err != nil { ++ return err, 0 ++ } ++ } ++ } ++ ++ ++ return nil, rev ++} ++ ++func (s *EtcdServer) leaseTimeToLive(ctx context.Context, r *pb.LeaseTimeToLiveRequest) (*pb.LeaseTimeToLiveResponse, error) { + if s.Leader() == s.ID() { + // primary; timetolive directly from leader + le := s.lessor.Lookup(lease.LeaseID(r.ID)) +diff --git a/integration/v3_auth_test.go b/integration/v3_auth_test.go +index ee386ff..b473053 100644 +--- a/integration/v3_auth_test.go ++++ b/integration/v3_auth_test.go +@@ -150,12 +150,10 @@ func testV3AuthWithLeaseRevokeWithRoot(t *testing.T, ccfg ClusterConfig) { + // wait for lease expire + time.Sleep(3 * time.Second) + +- tresp, terr := api.Lease.LeaseTimeToLive( ++ tresp, terr := rootc.TimeToLive( + context.TODO(), +- &pb.LeaseTimeToLiveRequest{ +- ID: int64(leaseID), +- Keys: true, +- }, ++ leaseID, ++ clientv3.WithAttachedKeys(), + ) + if terr != nil { + t.Error(terr) +@@ -394,3 +392,86 @@ func TestV3AuthOldRevConcurrent(t *testing.T) { + } + wg.Wait() + } ++ ++func TestV3AuthWithLeaseTimeToLive(t *testing.T) { ++ integration.BeforeTest(t) ++ clus := integration.NewCluster(t, &integration.ClusterConfig{Size: 1}) ++ defer clus.Terminate(t) ++ ++ users := []user{ ++ { ++ name: "user1", ++ password: "user1-123", ++ role: "role1", ++ key: "k1", ++ end: "k3", ++ }, ++ { ++ name: "user2", ++ password: "user2-123", ++ role: "role2", ++ key: "k2", ++ end: "k4", ++ }, ++ } ++ authSetupUsers(t, integration.ToGRPC(clus.Client(0)).Auth, users) ++ ++ authSetupRoot(t, integration.ToGRPC(clus.Client(0)).Auth) ++ ++ user1c, cerr := integration.NewClient(t, clientv3.Config{Endpoints: clus.Client(0).Endpoints(), Username: "user1", Password: "user1-123"}) ++ if cerr != nil { ++ t.Fatal(cerr) ++ } ++ defer user1c.Close() ++ ++ user2c, cerr := integration.NewClient(t, clientv3.Config{Endpoints: clus.Client(0).Endpoints(), Username: "user2", Password: "user2-123"}) ++ if cerr != nil { ++ t.Fatal(cerr) ++ } ++ defer user2c.Close() ++ ++ leaseResp, err := user1c.Grant(context.TODO(), 90) ++ if err != nil { ++ t.Fatal(err) ++ } ++ leaseID := leaseResp.ID ++ _, err = user1c.Put(context.TODO(), "k1", "val", clientv3.WithLease(leaseID)) ++ if err != nil { ++ t.Fatal(err) ++ } ++ // k2 can be accessed from both user1 and user2 ++ _, err = user1c.Put(context.TODO(), "k2", "val", clientv3.WithLease(leaseID)) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ _, err = user1c.TimeToLive(context.TODO(), leaseID) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ _, err = user2c.TimeToLive(context.TODO(), leaseID) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ _, err = user2c.TimeToLive(context.TODO(), leaseID, clientv3.WithAttachedKeys()) ++ if err == nil { ++ t.Fatal("timetolive from user2 should be failed with permission denied") ++ } ++ ++ rootc, cerr := integration.NewClient(t, clientv3.Config{Endpoints: clus.Client(0).Endpoints(), Username: "root", Password: "123"}) ++ if cerr != nil { ++ t.Fatal(cerr) ++ } ++ defer rootc.Close() ++ ++ if _, err := rootc.RoleRevokePermission(context.TODO(), "role1", "k1", "k3"); err != nil { ++ t.Fatal(err) ++ } ++ ++ _, err = user1c.TimeToLive(context.TODO(), leaseID, clientv3.WithAttachedKeys()) ++ if err == nil { ++ t.Fatal("timetolive from user2 should be failed with permission denied") ++ } ++} +diff --git a/pkg/ioutil/pagewriter_test.go b/pkg/ioutil/pagewriter_test.go +index 1061069..ee2fa0d 100644 +--- a/pkg/ioutil/pagewriter_test.go ++++ b/pkg/ioutil/pagewriter_test.go +@@ -37,7 +37,7 @@ func TestPageWriterRandom(t *testing.T) { + if cw.writeBytes > n { + t.Fatalf("wrote %d bytes to io.Writer, but only wrote %d bytes", cw.writeBytes, n) + } +- if n-cw.writeBytes > pageBytes { ++ if maxPendingBytes := pageBytes + defaultBufferBytes; n-cw.writeBytes > maxPendingBytes { + t.Fatalf("got %d bytes pending, expected less than %d bytes", n-cw.writeBytes, pageBytes) + } + t.Logf("total writes: %d", cw.writes) +diff --git a/tests/e2e/ctl_v3_auth_test.go b/tests/e2e/ctl_v3_auth_test.go +index 2142394..ca2524b 100644 +--- a/tests/e2e/ctl_v3_auth_test.go ++++ b/tests/e2e/ctl_v3_auth_test.go +@@ -69,6 +69,7 @@ func TestCtlV3AuthJWTExpire(t *testing.T) { testCtl(t, authTestJWTExpire, withCf + func TestCtlV3AuthCertCNAndUsernameNoPassword(t *testing.T) { + testCtl(t, authTestCertCNAndUsernameNoPassword, withCfg(configClientTLSCertAuth)) + } ++func TestCtlV3AuthLeaseTimeToLive(t *testing.T) { testCtl(t, authTestLeaseTimeToLive) } + + func authEnableTest(cx ctlCtx) { + if err := authEnable(cx); err != nil { +@@ -1130,3 +1131,51 @@ func authTestJWTExpire(cx ctlCtx) { + cx.t.Error(err) + } + } ++ ++func authTestLeaseTimeToLive(cx ctlCtx) { ++ if err := authEnable(cx); err != nil { ++ cx.t.Fatal(err) ++ } ++ cx.user, cx.pass = "root", "root" ++ ++ authSetupTestUser(cx) ++ ++ cx.user = "test-user" ++ cx.pass = "pass" ++ ++ leaseID, err := ctlV3LeaseGrant(cx, 10) ++ if err != nil { ++ cx.t.Fatal(err) ++ } ++ ++ err = ctlV3Put(cx, "foo", "val", leaseID) ++ if err != nil { ++ cx.t.Fatal(err) ++ } ++ ++ err = ctlV3LeaseTimeToLive(cx, leaseID, true) ++ if err != nil { ++ cx.t.Fatal(err) ++ } ++ ++ cx.user = "root" ++ cx.pass = "root" ++ err = ctlV3Put(cx, "bar", "val", leaseID) ++ if err != nil { ++ cx.t.Fatal(err) ++ } ++ ++ cx.user = "test-user" ++ cx.pass = "pass" ++ // the lease is attached to bar, which test-user cannot access ++ err = ctlV3LeaseTimeToLive(cx, leaseID, true) ++ if err == nil { ++ cx.t.Fatal("test-user must not be able to access to the lease, because it's attached to the key bar") ++ } ++ ++ // without --keys, access should be allowed ++ err = ctlV3LeaseTimeToLive(cx, leaseID, false) ++ if err != nil { ++ cx.t.Fatal(err) ++ } ++} +diff --git a/tests/e2e/ctl_v3_lease_test.go b/tests/e2e/ctl_v3_lease_test.go +index 608b8ca..64d2579 100644 +--- a/tests/e2e/ctl_v3_lease_test.go ++++ b/tests/e2e/ctl_v3_lease_test.go +@@ -294,3 +294,11 @@ func ctlV3LeaseRevoke(cx ctlCtx, leaseID string) error { + cmdArgs := append(cx.PrefixArgs(), "lease", "revoke", leaseID) + return spawnWithExpect(cmdArgs, fmt.Sprintf("lease %s revoked", leaseID)) + } ++ ++func ctlV3LeaseTimeToLive(cx ctlCtx, leaseID string, withKeys bool) error { ++ cmdArgs := append(cx.PrefixArgs(), "lease", "timetolive", leaseID) ++ if withKeys { ++ cmdArgs = append(cmdArgs, "--keys") ++ } ++ return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, fmt.Sprintf("lease %s granted with", leaseID)) ++} +-- +2.20.1 + diff --git a/etcd.spec b/etcd.spec index 85060c8..30e1e90 100644 --- a/etcd.spec +++ b/etcd.spec @@ -31,7 +31,7 @@ system.} %global gosupfiles integration/fixtures/* etcdserver/api/v2http/testdata/* Name: etcd -Release: 9 +Release: 10 Summary: Distributed reliable key-value store for the most critical data of a distributed system # Upstream license specification: Apache-2.0 @@ -51,6 +51,7 @@ Patch4: 0004-fix-CVE-2023-45288.patch Patch5: 0005-fix-CVE-2022-41723.patch Patch6: 0006-fix-CVE-2023-39325.patch Patch7: 0007-fix-CVE-2022-34038.patch +Patch8: 0008-fix-CVE-2023-32082.patch BuildRequires: golang BuildRequires: python3-devel @@ -160,6 +161,12 @@ getent passwd %{name} >/dev/null || useradd -r -g %{name} -d %{_sharedstatedir}/ %endif %changelog +* Mon Apr 22 2024 zhangbowei - 3.4.14-10 +- Type:bugfix +- CVE:NA +- SUG:NA +- DESC: fix CVE-2023-32082 + * Fri Apr 19 2024 zhangbowei - 3.4.14-9 - Type:bugfix - CVE:NA -- Gitee