164 lines
4.7 KiB
Go
164 lines
4.7 KiB
Go
package nginxactivation
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func TestActivationDirectoryTrust(t *testing.T) {
|
|
base := t.TempDir()
|
|
if err := os.Chmod(base, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := validateDirectory(base); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, mode := range []os.FileMode{0o755, 0o770, 0o777, 0o500} {
|
|
if err := os.Chmod(base, mode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := validateDirectory(base); err == nil {
|
|
t.Fatalf("不应接受目录权限 %o", mode)
|
|
}
|
|
}
|
|
if err := os.Chmod(base, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
parent := filepath.Join(base, "writable")
|
|
child := filepath.Join(parent, "activation")
|
|
if err := os.MkdirAll(child, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Chmod(parent, 0o777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := validateDirectory(child); err == nil {
|
|
t.Fatal("不应接受可由其他用户写入的父目录")
|
|
}
|
|
link := filepath.Join(base, "link")
|
|
if err := os.Symlink(child, link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, path := range []string{link, "relative", "/", base + "/../escape", base + ";injection"} {
|
|
if err := validateDirectory(path); err == nil {
|
|
t.Fatalf("不应接受目录 %q", path)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPrivateDirectoryKeepsTrustWithoutNginxSyntax(t *testing.T) {
|
|
base := t.TempDir()
|
|
for _, name := range []string{"管理证书", "application data", "literal;directory"} {
|
|
directory := filepath.Join(base, name)
|
|
if err := os.Mkdir(directory, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := validatePrivateDirectory(directory); err != nil {
|
|
t.Fatalf("应用私有状态目录不应套用 Nginx 指令字符限制:%v", err)
|
|
}
|
|
if err := validateDirectory(directory); err == nil {
|
|
t.Fatal("实际写入 Nginx 配置的目录仍须限制字符")
|
|
}
|
|
if err := os.Chmod(directory, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := validatePrivateDirectory(directory); err == nil {
|
|
t.Fatal("应用状态目录仍须保持私有权限")
|
|
}
|
|
}
|
|
parent := filepath.Join(base, "parent")
|
|
child := filepath.Join(parent, "child")
|
|
if err := os.MkdirAll(child, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Chmod(parent, 0o777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
link := filepath.Join(base, "link")
|
|
if err := os.Symlink(child, link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, directory := range []string{child, link, "relative", "/", base + "/../escape"} {
|
|
if err := validatePrivateDirectory(directory); err == nil {
|
|
t.Fatalf("不应接受不可信应用状态目录:%q", directory)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestActivationFilesRejectLinksAndSpecialFiles(t *testing.T) {
|
|
base := t.TempDir()
|
|
root, err := os.OpenRoot(base)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer root.Close()
|
|
if err := writeExclusive(root, "state", []byte("original")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := writeExclusive(root, "state", []byte("overwrite")); !errors.Is(err, os.ErrExist) {
|
|
t.Fatal("独占写入不应覆盖文件", err)
|
|
}
|
|
if _, err := readRegular(root, "state", 2); err == nil {
|
|
t.Fatal("不应读取超限文件")
|
|
}
|
|
if err := root.Symlink("state", "symlink"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := root.Link("state", "hardlink"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := unix.Mkfifo(filepath.Join(base, "fifo"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := root.Mkdir("directory", 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, path := range []string{"symlink", "hardlink", "state", "fifo", "directory"} {
|
|
if _, err := readRegular(root, path, 4096); err == nil {
|
|
t.Fatalf("不应读取不可信文件 %s", path)
|
|
}
|
|
if err := writeAtomic(root, path, []byte("replaced")); err == nil {
|
|
t.Fatalf("不应替换不可信文件 %s", path)
|
|
}
|
|
}
|
|
content, err := os.ReadFile(filepath.Join(base, "state"))
|
|
if err != nil || string(content) != "original" {
|
|
t.Fatal("被拒绝的操作改变了原文件", err)
|
|
}
|
|
if err := writeExclusive(root, "writable", []byte("untrusted")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := root.Chmod("writable", 0o666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := readRegular(root, "writable", 4096); err == nil {
|
|
t.Fatal("不应读取可由其他用户修改的状态")
|
|
}
|
|
}
|
|
|
|
func TestActivationAtomicWritePersistsPrivateState(t *testing.T) {
|
|
root, err := os.OpenRoot(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer root.Close()
|
|
for _, content := range [][]byte{[]byte("first"), []byte("second")} {
|
|
if err := writeAtomic(root, "state", content); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
actual, err := readRegular(root, "state", 4096)
|
|
if err != nil || !bytes.Equal(actual, content) {
|
|
t.Fatal("原子写入内容不一致", err)
|
|
}
|
|
info, err := root.Stat("state")
|
|
if err != nil || info.Mode().Perm() != 0o600 {
|
|
t.Fatal("状态文件权限不正确", err)
|
|
}
|
|
}
|
|
}
|