From 2dd515601800571d75680dab16c6bbd2978720c6 Mon Sep 17 00:00:00 2001 From: smail Date: Tue, 22 Sep 2026 03:26:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E8=BF=90=E8=A1=8C=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E4=B8=8E=E7=AE=A1=E7=90=86=E5=91=98=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E8=AF=BB=E5=8F=96=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/config/ssl_migration.go | 10 +++- backend/config/ssl_retention.go | 10 +++- .../services/adminreset/invocation_linux.go | 14 ++++- backend/utils/file/secure.go | 53 +++++++++++++++++++ backend/utils/runtimepath/layout.go | 17 +++++- 5 files changed, 98 insertions(+), 6 deletions(-) diff --git a/backend/config/ssl_migration.go b/backend/config/ssl_migration.go index b90ef46..674b2d0 100644 --- a/backend/config/ssl_migration.go +++ b/backend/config/ssl_migration.go @@ -16,7 +16,15 @@ func MigrateLegacySSLStorage(root string) error { if !runtimepath.DeploymentLayout(root) { return nil } - data, err := os.ReadFile(filepath.Join(root, "config", "config.json")) + configRoot, err := os.OpenRoot(filepath.Join(root, "config")) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + data, err := configRoot.ReadFile("config.json") + _ = configRoot.Close() if os.IsNotExist(err) { return nil } diff --git a/backend/config/ssl_retention.go b/backend/config/ssl_retention.go index e96251e..9ed0e59 100644 --- a/backend/config/ssl_retention.go +++ b/backend/config/ssl_retention.go @@ -148,7 +148,15 @@ func pruneManagedSSLLocked(root string, protected []SSLConfig) error { } } // 磁盘引用也必须保留,避免其他管理器实例或已提交未发布的候选被删除。 - data, err := os.ReadFile(filepath.Join(root, "config", "ssl.json")) + configRoot, err := os.OpenRoot(filepath.Join(root, "config")) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + data, err := configRoot.ReadFile("ssl.json") + _ = configRoot.Close() if err == nil { var section SSLSectionConfig if err := ParseStrict(data, §ion); err != nil { diff --git a/backend/services/adminreset/invocation_linux.go b/backend/services/adminreset/invocation_linux.go index a135e4f..d9357b8 100644 --- a/backend/services/adminreset/invocation_linux.go +++ b/backend/services/adminreset/invocation_linux.go @@ -120,7 +120,12 @@ func findStartCaller() (callerIdentity, error) { if err != nil || (executable != "/usr/bin/systemctl" && executable != "/bin/systemctl") { continue } - data, err := os.ReadFile(filepath.Join(base, "cmdline")) + procRoot, err := os.OpenRoot(base) + if err != nil { + continue + } + data, err := procRoot.ReadFile("cmdline") + _ = procRoot.Close() if err != nil { continue } @@ -132,7 +137,12 @@ func findStartCaller() (callerIdentity, error) { if !allowed { return callerIdentity{}, ErrUnauthorizedPeer } - status, err := os.ReadFile(filepath.Join(base, "status")) + procRoot, err = os.OpenRoot(base) + if err != nil { + return callerIdentity{}, ErrUnauthorizedPeer + } + status, err := procRoot.ReadFile("status") + _ = procRoot.Close() if err != nil || !rootProcessStatus(string(status)) { return callerIdentity{}, ErrUnauthorizedPeer } diff --git a/backend/utils/file/secure.go b/backend/utils/file/secure.go index fc2139a..9b74659 100644 --- a/backend/utils/file/secure.go +++ b/backend/utils/file/secure.go @@ -36,6 +36,59 @@ func ReadRegularFile(path string, maxSize int64) ([]byte, error) { return data, nil } +func OpenRegularFile(path string, flags int, perm os.FileMode) (*os.File, error) { + if strings.TrimSpace(path) == "" { + return nil, ErrUnsafeRegularFile + } + absolute, err := filepath.Abs(filepath.Clean(path)) + if err != nil { + return nil, ErrUnsafeRegularFile + } + root, err := os.OpenRoot(filepath.Dir(absolute)) + if err != nil { + return nil, ErrUnsafeRegularFile + } + defer root.Close() + + name := filepath.Base(absolute) + info, err := root.Lstat(name) + if err != nil && !os.IsNotExist(err) { + return nil, ErrUnsafeRegularFile + } + if err == nil && (info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular()) { + return nil, ErrUnsafeRegularFile + } + file, err := root.OpenFile(name, flags, perm) + if err != nil { + return nil, err + } + opened, err := file.Stat() + if err != nil || !opened.Mode().IsRegular() { + _ = file.Close() + return nil, ErrUnsafeRegularFile + } + return file, nil +} + +func CreateExclusiveFile(path string, perm os.FileMode) (*os.File, error) { + return OpenRegularFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, perm) +} + +func WriteRegularFile(path string, data []byte, perm os.FileMode) error { + if strings.TrimSpace(path) == "" { + return ErrUnsafeRegularFile + } + absolute, err := filepath.Abs(filepath.Clean(path)) + if err != nil { + return ErrUnsafeRegularFile + } + root, err := os.OpenRoot(filepath.Dir(absolute)) + if err != nil { + return ErrUnsafeRegularFile + } + defer root.Close() + return root.WriteFile(filepath.Base(absolute), data, perm) +} func ControlledRelativePath(base, value string) (string, error) { if strings.TrimSpace(value) == "" { return "", errors.New("path is required") diff --git a/backend/utils/runtimepath/layout.go b/backend/utils/runtimepath/layout.go index 000e08b..97ed9a4 100644 --- a/backend/utils/runtimepath/layout.go +++ b/backend/utils/runtimepath/layout.go @@ -180,7 +180,15 @@ func CheckReady(root string) error { return ErrMigrationRequired } // 已初始化部署缺失账号库或加密密钥时必须报错,不能按首次安装处理。 - raw, err := os.ReadFile(filepath.Join(root, "config", "config.json")) + configRoot, err := os.OpenRoot(filepath.Join(root, "config")) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + raw, err := configRoot.ReadFile("config.json") + _ = configRoot.Close() if os.IsNotExist(err) { return nil } @@ -279,7 +287,12 @@ func migrate(root string, rename func(string, string) error) ([]Move, error) { return nil, err } lockPath := filepath.Join(directory, migrationLock) - lock, err := os.OpenFile(lockPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) + lockRoot, err := os.OpenRoot(directory) + if err != nil { + return nil, err + } + lock, err := lockRoot.OpenFile(migrationLock, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) + _ = lockRoot.Close() if err != nil { return nil, errors.New("无法取得迁移锁;请确认没有其他迁移进程,并人工核对中断现场") }