From ce1b557ea920f80c1ea3f0e3504a46a87ca6e474 Mon Sep 17 00:00:00 2001 From: smail Date: Fri, 28 Aug 2026 14:54:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=9B=BA=E8=B6=85=E7=BA=A7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E6=9C=AC=E6=9C=BA=E9=87=8D=E7=BD=AE=E9=80=9A?= =?UTF-8?q?=E9=81=93=E6=9D=83=E9=99=90=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/services/adminreset/transport_test.go | 38 ++++++++++++++++++++ server/services/adminreset/transport_unix.go | 30 ++++++++++++++-- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/server/services/adminreset/transport_test.go b/server/services/adminreset/transport_test.go index 01ca5ab6..4fd7191a 100644 --- a/server/services/adminreset/transport_test.go +++ b/server/services/adminreset/transport_test.go @@ -91,3 +91,41 @@ func TestUnixEmptyAllowedUIDsRejectConnection(t *testing.T) { t.Fatalf("response = %+v", response) } } + +func TestUnixListenRejectsWritableControlDirectory(t *testing.T) { + parent := t.TempDir() + "/control" + if err := os.Mkdir(parent, 0700); err != nil { + t.Fatal(err) + } + if err := os.Chmod(parent, 0777); err != nil { + t.Fatal(err) + } + if _, err := ListenUnix(parent+"/control.sock", []uint32{uint32(os.Getuid())}); err == nil { + t.Fatal("writable control directory was accepted") + } +} + +func TestUnixControlSocketUsesRestrictedPermissions(t *testing.T) { + parent := t.TempDir() + "/control" + path := parent + "/control.sock" + server, err := ListenUnix(path, []uint32{uint32(os.Getuid())}) + if err != nil { + t.Fatal(err) + } + defer server.Close() + + parentInfo, err := os.Stat(parent) + if err != nil { + t.Fatal(err) + } + if parentInfo.Mode().Perm() != 0750 { + t.Fatalf("control directory permissions = %04o, want 0750", parentInfo.Mode().Perm()) + } + socketInfo, err := os.Lstat(path) + if err != nil { + t.Fatal(err) + } + if socketInfo.Mode().Perm() != 0600 { + t.Fatalf("control socket permissions = %04o, want 0600", socketInfo.Mode().Perm()) + } +} diff --git a/server/services/adminreset/transport_unix.go b/server/services/adminreset/transport_unix.go index 1c49f063..3f2f3dd7 100644 --- a/server/services/adminreset/transport_unix.go +++ b/server/services/adminreset/transport_unix.go @@ -30,8 +30,8 @@ func ListenUnix(path string, allowedUIDs []uint32) (*UnixServer, error) { if err := os.MkdirAll(parent, 0750); err != nil { return nil, fmt.Errorf("create control directory: %w", err) } - if info, err := os.Lstat(parent); err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() { - return nil, fmt.Errorf("control directory is not secure") + if err := validateControlDirectory(parent); err != nil { + return nil, err } if err := os.Chmod(parent, 0750); err != nil { return nil, fmt.Errorf("set control directory permissions: %w", err) @@ -40,6 +40,9 @@ func ListenUnix(path string, allowedUIDs []uint32) (*UnixServer, error) { if info.Mode()&os.ModeSymlink != 0 || info.Mode()&os.ModeSocket == 0 { return nil, fmt.Errorf("control path is not a socket") } + if err := validateControlSocketOwner(info); err != nil { + return nil, err + } if conn, dialErr := net.DialTimeout("unix", path, 100*time.Millisecond); dialErr == nil { _ = conn.Close() return nil, fmt.Errorf("control socket is already active") @@ -66,6 +69,29 @@ func ListenUnix(path string, allowedUIDs []uint32) (*UnixServer, error) { return &UnixServer{listener: listener, path: path, allowed: allowed}, nil } +func validateControlDirectory(path string) error { + info, err := os.Lstat(path) + if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() { + return fmt.Errorf("control directory is not secure") + } + if info.Mode().Perm()&0o022 != 0 { + return fmt.Errorf("control directory must not be writable by group or other users") + } + return validateControlOwner(info, "control directory") +} + +func validateControlSocketOwner(info os.FileInfo) error { + return validateControlOwner(info, "control socket") +} + +func validateControlOwner(info os.FileInfo, resource string) error { + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok || stat.Uid != uint32(os.Geteuid()) { + return fmt.Errorf("%s owner does not match service identity", resource) + } + return nil +} + func (s *UnixServer) Serve(ctx context.Context, handler ResetHandler, guard *SessionGuard) error { if s == nil || s.listener == nil || handler == nil || guard == nil { return ErrInvalidRequest