From fed36953943702ffc22dc9a00c33d17aa8373ee9 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Mon, 7 Sep 2026 21:39:12 +0200 Subject: [PATCH] fix(drivers): check retained package before reinstall --- .../preserve-retained-package-on-reinstall.md | 8 + go/internal/driverrepo/manager.go | 18 ++ .../retained_package_format_test.go | 213 ++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 .changeset/preserve-retained-package-on-reinstall.md create mode 100644 go/internal/driverrepo/retained_package_format_test.go diff --git a/.changeset/preserve-retained-package-on-reinstall.md b/.changeset/preserve-retained-package-on-reinstall.md new file mode 100644 index 00000000..0a549ac2 --- /dev/null +++ b/.changeset/preserve-retained-package-on-reinstall.md @@ -0,0 +1,8 @@ +--- +"ftw": patch +--- + +Check retained package metadata before reinstalling a driver from an older +database. Reject a change to direct-manifest format while a package envelope +remains, and require signed legacy metadata when its format is unknown. +A rejected reinstall preserves the active artifact and rollback record. diff --git a/go/internal/driverrepo/manager.go b/go/internal/driverrepo/manager.go index feec2e01..0873becd 100644 --- a/go/internal/driverrepo/manager.go +++ b/go/internal/driverrepo/manager.go @@ -594,6 +594,24 @@ func (m *Manager) installResolved(ctx context.Context, repo config.DriverReposit if err == nil && retained.RepositoryFormat != "" && retained.RepositoryFormat != repositoryFormat(repo) { return state.DriverRepoInstall{}, errors.New("retained driver metadata format cannot change") } + if repositoryFormat(repo) == config.DriverRepositoryFormatFTWManifestV1 { + // Older activation rows do not record a format. A retained envelope + // still rules out direct-manifest operation, even if it is damaged. + _, packageErr := os.Lstat(filepath.Join(filepath.Dir(installPath), sourcefulInstalledPackageEnvelope)) + if packageErr == nil { + return state.DriverRepoInstall{}, errors.New("retained signed package cannot be reinstalled as a direct-manifest driver") + } + if !errors.Is(packageErr, os.ErrNotExist) { + return state.DriverRepoInstall{}, fmt.Errorf("inspect retained signed package envelope: %w", packageErr) + } + if err == nil && retained.RepositoryFormat == "" { + // Missing metadata does not prove v1. Recover the old format only + // from signed metadata bound to that install's source and artifact. + if err := m.recordDirectManifestFormat(repo, retained); err != nil { + return state.DriverRepoInstall{}, err + } + } + } if err := atomicWrite(installPath, raw, 0o600); err != nil { return state.DriverRepoInstall{}, err } diff --git a/go/internal/driverrepo/retained_package_format_test.go b/go/internal/driverrepo/retained_package_format_test.go new file mode 100644 index 00000000..fc61a3d8 --- /dev/null +++ b/go/internal/driverrepo/retained_package_format_test.go @@ -0,0 +1,213 @@ +package driverrepo + +import ( + "bytes" + "context" + "errors" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/srcfl/ftw/go/internal/config" + "github.com/srcfl/ftw/go/internal/drivers" + "github.com/srcfl/ftw/go/internal/telemetry" +) + +type retainedPathSnapshot struct { + info os.FileInfo + raw []byte + target string +} + +func snapshotRetainedPath(t *testing.T, path string) *retainedPathSnapshot { + t.Helper() + info, err := os.Lstat(path) + if errors.Is(err, os.ErrNotExist) { + return nil + } + if err != nil { + t.Fatal(err) + } + snapshot := &retainedPathSnapshot{info: info} + if info.Mode()&os.ModeSymlink != 0 { + snapshot.target, err = os.Readlink(path) + } else { + snapshot.raw, err = os.ReadFile(path) + } + if err != nil { + t.Fatal(err) + } + return snapshot +} + +func assertRetainedPathUnchanged(t *testing.T, path string, before *retainedPathSnapshot) { + t.Helper() + after := snapshotRetainedPath(t, path) + if before == nil || after == nil { + if before != after { + t.Fatalf("retained path existence changed for %s", path) + } + return + } + if !os.SameFile(before.info, after.info) { + t.Errorf("retained path was replaced before install rejection: %s", path) + } + if before.info.Mode() != after.info.Mode() || before.target != after.target || !bytes.Equal(before.raw, after.raw) { + t.Errorf("retained path contents changed before install rejection: %s", path) + } +} + +func directAliasEntryForRetainedSourceful( + t *testing.T, + f *installedPolicyFixture, +) (*Manager, config.DriverRepositorySource, Manifest, ManifestDriver) { + t.Helper() + manager := f.manager(t, []config.DriverRepositorySource{f.repo}, "1.7.0") + repo, manifest, entry, err := manager.find(f.repo.ID, f.installed.DriverID, f.installed.Version) + if err != nil { + t.Fatal(err) + } + repo.Format = config.DriverRepositoryFormatFTWManifestV1 + entry.PackageID = "" + entry.Target = "" + entry.ArtifactID = "" + entry.RuntimeName = "" + entry.RuntimeSemantics = "" + entry.RuntimeVersion = "" + entry.RuntimeABI = "" + entry.HostAPIProfile = "" + entry.PackageKeyID = "" + entry.PackageEnvelopeURL = "" + entry.PackageEnvelopeSHA256 = "" + entry.SourceCommit = "" + entry.Channel = "" + entry.ControlEnabled = false + entry.Commands = nil + entry.DefaultMode = sourcefulDefaultMode{} + entry.LeasePolicy = sourcefulLeasePolicy{} + return manager, repo, manifest, entry +} + +func TestRetainedSourcefulPackageRejectsDirectAliasBeforeChanges(t *testing.T) { + for _, tc := range []struct { + name string + mutateEnvelope func(*testing.T, string) + }{ + {name: "valid envelope"}, + { + name: "corrupt envelope", + mutateEnvelope: func(t *testing.T, path string) { + if err := os.WriteFile(path, []byte("corrupt retained envelope"), 0o600); err != nil { + t.Fatal(err) + } + }, + }, + { + name: "dangling envelope symlink", + mutateEnvelope: func(t *testing.T, path string) { + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + if err := os.Symlink("missing-envelope", path); err != nil { + t.Fatal(err) + } + }, + }, + { + name: "missing canonical envelope", + mutateEnvelope: func(t *testing.T, path string) { + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + f := newInstalledSourcefulControlFixture(t) + f.setHistoricalUnknownFormat(t) + manager, repo, manifest, entry := directAliasEntryForRetainedSourceful(t, f) + envelopePath := filepath.Join(filepath.Dir(f.installed.InstalledPath), sourcefulInstalledPackageEnvelope) + activePath := filepath.Join( + manager.ActiveDir(), + filepath.FromSlash(strings.TrimPrefix(f.installed.LogicalPath, "drivers/")), + ) + if tc.mutateEnvelope != nil { + tc.mutateEnvelope(t, envelopePath) + } + + beforeRow := f.installRow(t) + beforeLua := snapshotRetainedPath(t, f.installed.InstalledPath) + beforeEnvelope := snapshotRetainedPath(t, envelopePath) + beforeActive := snapshotRetainedPath(t, activePath) + if beforeActive == nil || beforeActive.info.Mode()&os.ModeSymlink == 0 { + t.Fatalf("active logical path is not a symlink before reinstall: %s", activePath) + } + _, installErr := manager.installResolved(context.Background(), repo, manifest, entry) + + afterRow := f.installRow(t) + if installErr == nil { + t.Error("direct-manifest alias reclassified a retained Sourceful package") + } + if afterRow != beforeRow { + t.Errorf("active install row changed before rejection:\n before: %+v\n after: %+v", beforeRow, afterRow) + } + if afterRow.PreviousInstalledPath != beforeRow.PreviousInstalledPath { + t.Errorf("previous installed path changed from %q to %q", beforeRow.PreviousInstalledPath, afterRow.PreviousInstalledPath) + } + assertRetainedPathUnchanged(t, f.installed.InstalledPath, beforeLua) + assertRetainedPathUnchanged(t, envelopePath, beforeEnvelope) + assertRetainedPathUnchanged(t, activePath, beforeActive) + }) + } +} + +func TestHistoricalSourcefulPackageAllowsCanonicalReinstall(t *testing.T) { + f := newInstalledSourcefulControlFixture(t) + f.setHistoricalUnknownFormat(t) + manager := f.manager(t, []config.DriverRepositorySource{f.repo}, "1.7.0") + + installed, err := manager.Install(context.Background(), f.repo.ID, f.installed.DriverID, f.installed.Version) + if err != nil { + t.Fatalf("canonical Sourceful reinstall: %v", err) + } + if installed.RepositoryFormat != config.DriverRepositoryFormatSourcefulIndexV1 || !installed.Active { + t.Fatalf("canonical reinstalled row = %+v", installed) + } + if _, err := os.Stat(filepath.Join(filepath.Dir(installed.InstalledPath), sourcefulInstalledPackageEnvelope)); err != nil { + t.Fatalf("canonical package envelope after reinstall: %v", err) + } +} + +func TestHistoricalDirectManifestAllowsVerifiedReinstallAndDefault(t *testing.T) { + f := installLegacyDirectFixture(t) + f.clearFormat(t, nil) + store, manager := f.reopen(t) + envelopePath := filepath.Join(filepath.Dir(f.installed.InstalledPath), sourcefulInstalledPackageEnvelope) + if _, err := os.Lstat(envelopePath); !errors.Is(err, os.ErrNotExist) { + t.Fatalf("legacy direct install has package envelope: %v", err) + } + + installed, err := manager.Install(context.Background(), f.repo.ID, f.installed.DriverID, f.installed.Version) + if err != nil { + t.Fatalf("verified direct-manifest reinstall: %v", err) + } + if installed.RepositoryFormat != config.DriverRepositoryFormatFTWManifestV1 || !installed.Active { + t.Fatalf("verified direct-manifest row = %+v", installed) + } + stored, err := store.ActiveDriverRepoInstall(installed.LogicalPath) + if err != nil || stored.RepositoryFormat != config.DriverRepositoryFormatFTWManifestV1 { + t.Fatalf("stored direct-manifest row = %+v, %v", stored, err) + } + + registry := drivers.NewRegistry(telemetry.NewStore()) + registry.RuntimePolicyResolver = manager.RuntimePolicy + defer registry.ShutdownAll() + driver := f.legacyDriver() + if err := registry.Add(context.Background(), driver); err != nil { + t.Fatalf("start verified direct-manifest driver: %v", err) + } + if err := registry.SendDefault(context.Background(), driver.Name); err != nil { + t.Fatalf("run verified direct-manifest default: %v", err) + } +}