Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NixOS support] Run patchelf after autoupdate download #1468

Merged
merged 11 commits into from
Nov 22, 2023
9 changes: 6 additions & 3 deletions ee/tuf/autoupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions ee/tuf/autoupdate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 69 additions & 0 deletions ee/tuf/finalize_linux.go
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 8 additions & 0 deletions ee/tuf/finalize_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//go:build !linux
// +build !linux

package tuf

func patchExecutable(executableLocation string) error {
return nil
}
17 changes: 17 additions & 0 deletions ee/tuf/finalize_other_test.go
Original file line number Diff line number Diff line change
@@ -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(""))
}
5 changes: 5 additions & 0 deletions ee/tuf/library_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 16 additions & 6 deletions pkg/allowedcmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
)

type AllowedCommand func(ctx context.Context, arg ...string) (*exec.Cmd, error)
Expand Down Expand Up @@ -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
Comment on lines +46 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also use a pointer to a bool. But this is fine

)

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
}
4 changes: 4 additions & 0 deletions pkg/allowedcmd/cmd_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
}
Expand Down
Loading