diff --git a/ee/tuf/autoupdate.go b/ee/tuf/autoupdate.go index b8aaa52c9..9bf34159f 100644 --- a/ee/tuf/autoupdate.go +++ b/ee/tuf/autoupdate.go @@ -328,9 +328,12 @@ func (ta *TufAutoupdater) checkForUpdate() error { // If launcher was updated, we want to exit and reload if updatedVersion, ok := updatesDownloaded[binaryLauncher]; ok { - level.Debug(ta.logger).Log("msg", "launcher updated -- exiting to load new version", "new_binary_version", updatedVersion) - ta.signalRestart <- NewLauncherReloadNeededErr(updatedVersion) - return nil + // Only reload if we're not using a localdev path + if ta.knapsack.LocalDevelopmentPath() == "" { + level.Debug(ta.logger).Log("msg", "launcher updated -- exiting to load new version", "new_binary_version", updatedVersion) + ta.signalRestart <- NewLauncherReloadNeededErr(updatedVersion) + return nil + } } // For non-launcher binaries (i.e. osqueryd), call any reload functions we have saved diff --git a/ee/tuf/autoupdate_test.go b/ee/tuf/autoupdate_test.go index b45a6246f..207754feb 100644 --- a/ee/tuf/autoupdate_test.go +++ b/ee/tuf/autoupdate_test.go @@ -73,6 +73,7 @@ func TestExecute_launcherUpdate(t *testing.T) { mockKnapsack.On("TufServerURL").Return(tufServerUrl) mockKnapsack.On("UpdateDirectory").Return("") mockKnapsack.On("MirrorServerURL").Return("https://example.com") + mockKnapsack.On("LocalDevelopmentPath").Return("") mockQuerier := newMockQuerier(t) // Set up autoupdater diff --git a/ee/tuf/finalize_linux.go b/ee/tuf/finalize_linux.go new file mode 100644 index 000000000..395eb5fc4 --- /dev/null +++ b/ee/tuf/finalize_linux.go @@ -0,0 +1,69 @@ +//go:build linux +// +build linux + +package tuf + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "github.com/kolide/launcher/pkg/allowedcmd" +) + +// patchExecutable updates the downloaded binary as necessary for it to be able to +// run on this system. On NixOS, we have to set the interpreter for any non-NixOS +// executable we want to run. +// See: https://unix.stackexchange.com/a/522823 +func patchExecutable(executableLocation string) error { + if !allowedcmd.IsNixOS() { + return nil + } + + interpreter, err := getInterpreter(executableLocation) + if err != nil { + return fmt.Errorf("getting interpreter for %s: %w", executableLocation, err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cmd, err := allowedcmd.Patchelf(ctx, "--set-interpreter", interpreter, executableLocation) + if err != nil { + return fmt.Errorf("creating patchelf command: %w", err) + } + + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("running patchelf: output `%s`, error `%w`", string(out), err) + } + + return nil +} + +// getInterpreter asks patchelf what the interpreter is for the current running +// executable, assuming that's a reasonable choice given that the current executable +// is able to run. +func getInterpreter(executableLocation string) (string, error) { + currentExecutable, err := os.Executable() + if err != nil { + return "", fmt.Errorf("getting current running executable: %w", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cmd, err := allowedcmd.Patchelf(ctx, "--print-interpreter", currentExecutable) + if err != nil { + return "", fmt.Errorf("creating patchelf command: %w", err) + } + + out, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("running patchelf: output `%s`, error `%w`", string(out), err) + + } + + return strings.TrimSpace(string(out)), nil +} diff --git a/ee/tuf/finalize_other.go b/ee/tuf/finalize_other.go new file mode 100644 index 000000000..b645ffbfe --- /dev/null +++ b/ee/tuf/finalize_other.go @@ -0,0 +1,8 @@ +//go:build !linux +// +build !linux + +package tuf + +func patchExecutable(executableLocation string) error { + return nil +} diff --git a/ee/tuf/finalize_other_test.go b/ee/tuf/finalize_other_test.go new file mode 100644 index 000000000..8458c18a4 --- /dev/null +++ b/ee/tuf/finalize_other_test.go @@ -0,0 +1,17 @@ +//go:build !linux +// +build !linux + +package tuf + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_patchExecutable(t *testing.T) { + t.Parallel() + + // patchExecutable is a no-op on windows and darwin + require.NoError(t, patchExecutable("")) +} diff --git a/ee/tuf/library_manager.go b/ee/tuf/library_manager.go index 269934799..bd01ff61f 100644 --- a/ee/tuf/library_manager.go +++ b/ee/tuf/library_manager.go @@ -201,6 +201,11 @@ func (ulm *updateLibraryManager) moveVerifiedUpdate(binary autoupdatableBinary, return fmt.Errorf("could not set +x permissions on executable: %w", err) } + // If necessary, patch the executable (NixOS only) + if err := patchExecutable(executableLocation(stagedVersionedDirectory, binary)); err != nil { + return fmt.Errorf("could not patch executable: %w", err) + } + // Validate the executable if err := autoupdate.CheckExecutable(context.TODO(), executableLocation(stagedVersionedDirectory, binary), "--version"); err != nil { return fmt.Errorf("could not verify executable: %w", err) diff --git a/pkg/allowedcmd/cmd.go b/pkg/allowedcmd/cmd.go index eb054b761..128908a51 100644 --- a/pkg/allowedcmd/cmd.go +++ b/pkg/allowedcmd/cmd.go @@ -6,7 +6,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" ) type AllowedCommand func(ctx context.Context, arg ...string) (*exec.Cmd, error) @@ -38,14 +37,25 @@ func validatedCommand(ctx context.Context, knownPath string, arg ...string) (*ex } func allowSearchPath() bool { - if runtime.GOOS != "linux" { - return false + return IsNixOS() +} + +// Save results of lookup so we don't have to stat for /etc/NIXOS every time +// we want to know. +var ( + checkedIsNixOS = false + isNixOS = false +) + +func IsNixOS() bool { + if checkedIsNixOS { + return isNixOS } - // We only allow searching for binaries in PATH on NixOS if _, err := os.Stat("/etc/NIXOS"); err == nil { - return true + isNixOS = true } - return false + checkedIsNixOS = true + return isNixOS } diff --git a/pkg/allowedcmd/cmd_linux.go b/pkg/allowedcmd/cmd_linux.go index 4923644ed..35c08d78e 100644 --- a/pkg/allowedcmd/cmd_linux.go +++ b/pkg/allowedcmd/cmd_linux.go @@ -95,6 +95,10 @@ func Pacman(ctx context.Context, arg ...string) (*exec.Cmd, error) { return validatedCommand(ctx, "/usr/bin/pacman", arg...) } +func Patchelf(ctx context.Context, arg ...string) (*exec.Cmd, error) { + return validatedCommand(ctx, "/run/current-system/sw/bin/patchelf", arg...) +} + func Ps(ctx context.Context, arg ...string) (*exec.Cmd, error) { return validatedCommand(ctx, "/usr/bin/ps", arg...) }