Files
Privatae e4c9b69b46
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s
bastion 0.53.2
2026-10-20 04:42:14 +00:00

104 lines
3.9 KiB
Go

package main
// Fuzz targets for the path walls that take a name from a host and turn it
// into a path on this machine. Each one states the property the wall exists
// for and lets the fuzzer look for an input that breaks it. The seeds run on
// every `go test`; deploy/fuzz.sh runs each target for a bounded time.
//
// These two are pure (no filesystem state), so they run on every platform.
// The walls that follow symlinks are in fuzz_paths_unix_test.go.
import (
"os"
"path/filepath"
"strings"
"testing"
)
// cleanRel is the traversal wall every store read, context write and session
// log goes through. Whatever the host sends, an accepted path is strictly
// inside the root: never the root itself, never beside it, never above it.
func FuzzCleanRelStaysInsideTheRoot(f *testing.F) {
for _, s := range []string{
"a", "a/b", "sub/a.txt", "..", "../x", "a/../../x", "a/..", ".", "./a", "a//b",
"/etc/passwd", "a\x00b", " a ", "..\\x", "a/../b/../../c", "....", "...",
"a/./../..", "\u2025/x", "a/b/c/../../..", "C:\\x", "\\\\host\\share",
} {
f.Add(s)
}
root := filepath.Join(f.TempDir(), "root")
f.Fuzz(func(t *testing.T, rpath string) {
got, ok := cleanRel(root, rpath)
if !ok {
if got != "" {
t.Fatalf("a refusal returned a path: %q", got)
}
return
}
if !strings.HasPrefix(got, root+string(os.PathSeparator)) {
t.Fatalf("cleanRel(%q) = %q, which is not strictly inside %q", rpath, got, root)
}
rel, err := filepath.Rel(root, got)
if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
t.Fatalf("cleanRel(%q) = %q, which is outside %q (rel %q)", rpath, got, root, rel)
}
if got != filepath.Clean(got) {
t.Fatalf("cleanRel(%q) = %q is not clean", rpath, got)
}
})
}
// A service slot's record, data dir and socket are joined from the name and
// socket the host sends. Once validateServiceSpec accepts them, each must be
// exactly one path element: the record lands in the services dir, the data dir
// is <services>/<name>/data, and the socket sits directly in that data dir.
func FuzzServiceSlotPathsStayInTheirDirectory(f *testing.F) {
for _, s := range [][3]string{
{"gitea", "gitea.sock", "/data"},
{"../x", "gitea.sock", "/data"},
{"a", "../../x.sock", "/data"},
{"a", ".", "/data"},
{"a", "..", "/data"},
{"a", "s/../../x", "/data"},
{"a", "x.sock", "/"},
{"a", "x.sock", "/data/../.."},
{"a", "x.sock", "c:/data"},
{"a-", "x.sock", "/srv/data"},
{"A", "x.sock", "/data"},
{"a", "x\x00.sock", "/data"},
} {
f.Add(s[0], s[1], s[2])
}
services := filepath.Join(f.TempDir(), "services")
f.Fuzz(func(t *testing.T, name, socket, dataPath string) {
spec := harnessSpec{ServiceName: name, Image: "registry.example.com/app:1", Socket: socket,
DataPath: dataPath, DataUID: 1000}
if validateServiceSpec(spec) != "" {
return
}
rec := filepath.Join(services, name+".json")
if filepath.Dir(rec) != services || filepath.Base(rec) != name+".json" {
t.Fatalf("the record for %q lands at %q, outside %q", name, rec, services)
}
data := filepath.Join(services, name, "data")
if filepath.Dir(filepath.Dir(data)) != services || filepath.Base(filepath.Dir(data)) != name {
t.Fatalf("the data dir for %q is %q, not <services>/%s/data", name, data, name)
}
sock := filepath.Join(data, socket)
if filepath.Dir(sock) != data || filepath.Base(sock) != socket {
t.Fatalf("socket %q joins to %q, which is not directly in %q", socket, sock, data)
}
// data_path is a path inside the container; it must name one clean
// absolute place other than the container's root: every segment a
// real name, none empty, "." or "..".
if !strings.HasPrefix(dataPath, "/") || dataPath == "/" {
t.Fatalf("data_path %q was accepted", dataPath)
}
for _, seg := range strings.Split(dataPath[1:], "/") {
if seg == "" || seg == "." || seg == ".." {
t.Fatalf("data_path %q was accepted, with the segment %q", dataPath, seg)
}
}
})
}