fix: preserve snapshot symlinks and guard incomplete upgrade copies
This commit is contained in:
@@ -100,7 +100,7 @@ func TestUpgradeLifecycle(t *testing.T) {
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("requires chown")
|
||||
}
|
||||
for _, mode := range []string{"success", "legacy-layout", "shared-layout", "start-failure", "readiness-failure", "cleanup-failure", "running", "running-failure", "restart-failure", "delete-failure", "invalid-image", "unverified-image", "failed-build", "version-mismatch", "image-id-mismatch", "db-failure"} {
|
||||
for _, mode := range []string{"snapshot-fifo", "snapshot-dangling-link", "success", "legacy-layout", "shared-layout", "start-failure", "readiness-failure", "cleanup-failure", "running", "running-failure", "restart-failure", "delete-failure", "invalid-image", "unverified-image", "failed-build", "version-mismatch", "image-id-mismatch", "db-failure"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
image := "sha256:" + strings.Repeat("b", 64)
|
||||
var a *API
|
||||
@@ -260,6 +260,16 @@ func TestUpgradeLifecycle(t *testing.T) {
|
||||
if err := os.WriteFile(filepath.Join(home, ".config", "state"), []byte("original"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mode == "snapshot-dangling-link" {
|
||||
if err := os.Symlink("missing", filepath.Join(home, "dangling")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if mode == "snapshot-fifo" {
|
||||
if err := syscall.Mkfifo(filepath.Join(home, "pipe"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
i := store.Instance{ID: "one", UID: "owner", Name: "one", ContainerID: "old", ImageID: "old-image", Version: "v1", Status: "exited", Created: time.Now()}
|
||||
if err := a.s.AddInstance(i); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -297,6 +307,38 @@ func TestUpgradeLifecycle(t *testing.T) {
|
||||
if err := a.s.DB.QueryRow("SELECT phase,snapshot,failed_home FROM upgrade_journal WHERE instance_id='one'").Scan(&phase, &snapshot, &failed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mode == "snapshot-fifo" {
|
||||
if w.Code != 500 || !strings.Contains(w.Body.String(), "home/pipe") || phase != "rolled_back" || got.ContainerID != "old" {
|
||||
t.Fatalf("%d %s phase=%s", w.Code, w.Body.String(), phase)
|
||||
}
|
||||
entries, err := os.ReadDir(base)
|
||||
if err != nil || len(entries) != 1 || entries[0].Name() != "home" {
|
||||
t.Fatalf("partial snapshot leaked: %v %v", entries, err)
|
||||
}
|
||||
listed := configCall(a, got, "GET", "snapshots", "")
|
||||
if listed.Code != 200 || strings.TrimSpace(listed.Body.String()) != "[]" {
|
||||
t.Fatal(listed.Code, listed.Body.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
if mode == "snapshot-dangling-link" {
|
||||
if w.Code != 200 || phase != "complete" || got.ContainerID != "new" {
|
||||
t.Fatalf("unexpected result: %d %s phase=%s instance=%+v", w.Code, w.Body.String(), phase, got)
|
||||
}
|
||||
saved, err := os.ReadFile(filepath.Join(base, snapshot, ".config", "state"))
|
||||
if err != nil || string(saved) != "original" {
|
||||
t.Fatalf("copied data missing: %q %v", saved, err)
|
||||
}
|
||||
link, err := os.Readlink(filepath.Join(base, snapshot, "dangling"))
|
||||
if err != nil || link != "missing" {
|
||||
t.Fatalf("copied link missing: %q %v", link, err)
|
||||
}
|
||||
listed := configCall(a, got, "GET", "snapshots", "")
|
||||
if listed.Code != 200 || !strings.Contains(listed.Body.String(), snapshot) {
|
||||
t.Fatalf("snapshot not listed: %d %s", listed.Code, listed.Body.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(home, ".config", "state"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -375,7 +417,7 @@ func TestUpgradeLifecycle(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpgradeSnapshotRejectsUnsafeFiles(t *testing.T) {
|
||||
for _, kind := range []string{"symlink", "fifo", "oversize", "directory-symlink"} {
|
||||
for _, kind := range []string{"fifo", "oversize"} {
|
||||
t.Run(kind, func(t *testing.T) {
|
||||
base := t.TempDir()
|
||||
if err := os.Mkdir(filepath.Join(base, "home"), 0700); err != nil {
|
||||
@@ -384,14 +426,10 @@ func TestUpgradeSnapshotRejectsUnsafeFiles(t *testing.T) {
|
||||
p := filepath.Join(base, "home", "bad")
|
||||
var err error
|
||||
switch kind {
|
||||
case "symlink":
|
||||
err = os.Symlink("/etc/passwd", p)
|
||||
case "fifo":
|
||||
err = syscall.Mkfifo(p, 0600)
|
||||
case "oversize":
|
||||
err = os.WriteFile(p, []byte("12345"), 0600)
|
||||
case "directory-symlink":
|
||||
err = os.Symlink("/tmp", p)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -412,7 +450,7 @@ func TestUpgradeSnapshotRelativeLinks(t *testing.T) {
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("requires chown")
|
||||
}
|
||||
for _, mode := range []string{"pnpm", "escape", "chain-escape", "loop", "dangling"} {
|
||||
for _, mode := range []string{"pnpm", "absolute", "escape", "chain-escape", "loop", "dangling"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
base := t.TempDir()
|
||||
home := filepath.Join(base, "home")
|
||||
@@ -425,6 +463,8 @@ func TestUpgradeSnapshotRelativeLinks(t *testing.T) {
|
||||
}
|
||||
link := ".pnpm/pkg/node_modules/pkg"
|
||||
switch mode {
|
||||
case "absolute":
|
||||
link = "/workspace/example"
|
||||
case "escape":
|
||||
link = "../../outside"
|
||||
case "chain-escape":
|
||||
@@ -446,12 +486,6 @@ func TestUpgradeSnapshotRelativeLinks(t *testing.T) {
|
||||
}
|
||||
defer root.Close()
|
||||
err = upgradeCopy(context.Background(), root, "home", "snapshot", 1024)
|
||||
if mode != "pnpm" {
|
||||
if err == nil {
|
||||
t.Fatal("unsafe link accepted")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -465,6 +499,13 @@ func TestUpgradeSnapshotRelativeLinks(t *testing.T) {
|
||||
if err := root.Rename("snapshot", "home"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mode != "pnpm" {
|
||||
got, err := root.Readlink("home/node_modules/pkg")
|
||||
if err != nil || got != link {
|
||||
t.Fatalf("restored link changed: %q %v", got, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(home, "node_modules/pkg/index.js"))
|
||||
if err != nil || string(data) != "original" {
|
||||
t.Fatalf("rollback link broken: %q %v", data, err)
|
||||
|
||||
Reference in New Issue
Block a user