From 9751a5244daa87f13310b12c189bedd7fbbcbb5f Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Wed, 12 May 2021 18:52:31 +0200 Subject: [PATCH] Automatically fix chrome-sandbox permissions on older kernels --- cmds/portmaster-start/run.go | 5 +- cmds/portmaster-start/show.go | 5 +- cmds/portmaster-start/update.go | 41 ++++++++-------- updates/get.go | 8 +--- updates/helper/electron.go | 84 +++++++++++++++++++++++++++++++++ updates/upgrader.go | 11 +++-- 6 files changed, 122 insertions(+), 32 deletions(-) create mode 100644 updates/helper/electron.go diff --git a/cmds/portmaster-start/run.go b/cmds/portmaster-start/run.go index a86f7576..f36e5d80 100644 --- a/cmds/portmaster-start/run.go +++ b/cmds/portmaster-start/run.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/safing/portmaster/updates/helper" "github.com/spf13/cobra" "github.com/tevino/abool" ) @@ -283,7 +284,9 @@ func persistOutputStreams(opts *Options, version string, cmd *exec.Cmd) (chan st } func execute(opts *Options, args []string) (cont bool, err error) { - file, err := registry.GetFile(platform(opts.Identifier)) + file, err := registry.GetFile( + helper.PlatformIdentifier(opts.Identifier), + ) if err != nil { return true, fmt.Errorf("could not get component: %w", err) } diff --git a/cmds/portmaster-start/show.go b/cmds/portmaster-start/show.go index cc6d999c..8406b0b0 100644 --- a/cmds/portmaster-start/show.go +++ b/cmds/portmaster-start/show.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/safing/portmaster/updates/helper" "github.com/spf13/cobra" ) @@ -30,7 +31,9 @@ func show(opts *Options, cmdArgs []string) error { opts.Identifier += exeSuffix } - file, err := registry.GetFile(platform(opts.Identifier)) + file, err := registry.GetFile( + helper.PlatformIdentifier(opts.Identifier), + ) if err != nil { return fmt.Errorf("could not get component: %s", err) } diff --git a/cmds/portmaster-start/update.go b/cmds/portmaster-start/update.go index 562c7877..13703d8e 100644 --- a/cmds/portmaster-start/update.go +++ b/cmds/portmaster-start/update.go @@ -4,9 +4,9 @@ import ( "context" "fmt" "os" - "runtime" "github.com/safing/portbase/log" + "github.com/safing/portmaster/updates/helper" "github.com/spf13/cobra" ) @@ -52,31 +52,31 @@ func downloadUpdates() error { // mark required updates if onWindows { registry.MandatoryUpdates = []string{ - platform("core/portmaster-core.exe"), - platform("kext/portmaster-kext.dll"), - platform("kext/portmaster-kext.sys"), - platform("start/portmaster-start.exe"), - platform("notifier/portmaster-notifier.exe"), - platform("notifier/portmaster-snoretoast.exe"), + helper.PlatformIdentifier("core/portmaster-core.exe"), + helper.PlatformIdentifier("kext/portmaster-kext.dll"), + helper.PlatformIdentifier("kext/portmaster-kext.sys"), + helper.PlatformIdentifier("start/portmaster-start.exe"), + helper.PlatformIdentifier("notifier/portmaster-notifier.exe"), + helper.PlatformIdentifier("notifier/portmaster-snoretoast.exe"), } } else { registry.MandatoryUpdates = []string{ - platform("core/portmaster-core"), - platform("start/portmaster-start"), - platform("notifier/portmaster-notifier"), + helper.PlatformIdentifier("core/portmaster-core"), + helper.PlatformIdentifier("start/portmaster-start"), + helper.PlatformIdentifier("notifier/portmaster-notifier"), } } - // add updates that we require on all platforms. + // add updates that we require on all helper.PlatformIdentifiers. registry.MandatoryUpdates = append( registry.MandatoryUpdates, - platform("app/portmaster-app.zip"), + helper.PlatformIdentifier("app/portmaster-app.zip"), "all/ui/modules/portmaster.zip", ) // Add assets that need unpacking. registry.AutoUnpack = []string{ - platform("app/portmaster-app.zip"), + helper.PlatformIdentifier("app/portmaster-app.zip"), } // logging is configured as a persistent pre-run method inherited from @@ -93,11 +93,11 @@ func downloadUpdates() error { // Delete storage. err = os.RemoveAll(registry.StorageDir().Path) if err != nil { - return fmt.Errorf("failed to reset update dir: %s", err) + return fmt.Errorf("failed to reset update dir: %w", err) } err = registry.StorageDir().Ensure() if err != nil { - return fmt.Errorf("failed to create update dir: %s", err) + return fmt.Errorf("failed to create update dir: %w", err) } // Reset registry state. @@ -120,7 +120,12 @@ func downloadUpdates() error { registry.SelectVersions() err = registry.UnpackResources() if err != nil { - return fmt.Errorf("failed to unpack resources: %s", err) + return fmt.Errorf("failed to unpack resources: %w", err) + } + + // Fix chrome-sandbox permissions + if err := helper.EnsureChromeSandboxPermissions(registry); err != nil { + return fmt.Errorf("failed to fix electron permissions: %w", err) } return nil @@ -141,7 +146,3 @@ func purge() error { registry.Purge(3) return nil } - -func platform(identifier string) string { - return fmt.Sprintf("%s_%s/%s", runtime.GOOS, runtime.GOARCH, identifier) -} diff --git a/updates/get.go b/updates/get.go index 876b2d09..cc16e700 100644 --- a/updates/get.go +++ b/updates/get.go @@ -1,19 +1,15 @@ package updates import ( - "fmt" "path" - "runtime" "github.com/safing/portbase/updater" + "github.com/safing/portmaster/updates/helper" ) // GetPlatformFile returns the latest platform specific file identified by the given identifier. func GetPlatformFile(identifier string) (*updater.File, error) { - identifier = path.Join(fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH), identifier) - // From https://golang.org/pkg/runtime/#GOARCH - // GOOS is the running program's operating system target: one of darwin, freebsd, linux, and so on. - // GOARCH is the running program's architecture target: one of 386, amd64, arm, s390x, and so on. + identifier = helper.PlatformIdentifier(identifier) file, err := registry.GetFile(identifier) if err != nil { diff --git a/updates/helper/electron.go b/updates/helper/electron.go new file mode 100644 index 00000000..27dffcb3 --- /dev/null +++ b/updates/helper/electron.go @@ -0,0 +1,84 @@ +package helper + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/safing/portbase/log" + "github.com/safing/portbase/updater" +) + +var pmElectronUpdate *updater.File + +// EnsureChromeSandboxPermissions makes sure the chrome-sandbox distributed +// by our app-electron package has the SUID bit set on systems that do not +// allow unprivileged CLONE_NEWUSER (clone(3)). +// On non-linux systems or systems that have kernel.unprivileged_userns_clone +// set to 1 EnsureChromeSandboPermissions is a NO-OP +func EnsureChromeSandboxPermissions(reg *updater.ResourceRegistry) error { + if runtime.GOOS != "linux" { + return nil + } + + if checkSysctl("kernel.unprivileged_userns_clone", '1') { + log.Infof("Kernel support for unprivileged USERNS_CLONE enabled.") + return nil + } + + if pmElectronUpdate != nil && !pmElectronUpdate.UpgradeAvailable() { + return nil + } + identifier := PlatformIdentifier("app/portmaster-app.zip") + + log.Infof("Kernel support for unprivileged USERNS_CLONE disabled.") + + var err error + pmElectronUpdate, err = reg.GetFile(identifier) + if err != nil { + return err + } + unpackedPath := strings.TrimSuffix( + pmElectronUpdate.Path(), + filepath.Ext(pmElectronUpdate.Path()), + ) + sandboxFile := filepath.Join(unpackedPath, "chrome-sandbox") + if err := os.Chmod(sandboxFile, 0755|os.ModeSetuid); err != nil { + return err + } + log.Infof("Fixed SUID permissions for chrome-sandbox") + + return nil +} + +// PlatformIdentifier converts identifier for the current platform. +func PlatformIdentifier(identifier string) string { + // From https://golang.org/pkg/runtime/#GOARCH + // GOOS is the running program's operating system target: one of darwin, freebsd, linux, and so on. + // GOARCH is the running program's architecture target: one of 386, amd64, arm, s390x, and so on. + return fmt.Sprintf("%s_%s/%s", runtime.GOOS, runtime.GOARCH, identifier) +} + +func checkSysctl(setting string, value byte) bool { + c, err := sysctl(setting) + if err != nil { + return false + } + if len(c) < 1 { + return false + } + return c[0] == value +} + +func sysctl(setting string) ([]byte, error) { + parts := append([]string{"/proc", "sys"}, strings.Split(setting, ".")...) + path := filepath.Join(parts...) + content, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return content, nil +} diff --git a/updates/upgrader.go b/updates/upgrader.go index 95c0a4e2..b8559940 100644 --- a/updates/upgrader.go +++ b/updates/upgrader.go @@ -21,6 +21,7 @@ import ( "github.com/safing/portbase/notifications" "github.com/safing/portbase/rng" "github.com/safing/portbase/updater" + "github.com/safing/portmaster/updates/helper" ) const ( @@ -65,14 +66,16 @@ func upgrader(_ context.Context, _ interface{}) error { binBaseName := strings.Split(filepath.Base(os.Args[0]), "_")[0] switch binBaseName { case "portmaster-core": - err = upgradeCoreNotify() - if err != nil { + if err := upgradeCoreNotify(); err != nil { log.Warningf("updates: failed to notify about core upgrade: %s", err) } + if err := helper.EnsureChromeSandboxPermissions(registry); err != nil { + log.Warningf("updates: failed to handle electron upgrade: %s", err) + } + case "spn-hub": - err = upgradeHub() - if err != nil { + if err := upgradeHub(); err != nil { log.Warningf("updates: failed to initiate hub upgrade: %s", err) } }