commit e68ab9154e73f2fde78ffaf556f39670e4a807f5 Author: Yawning Angel yawning@schwanenlied.me Date: Wed Dec 7 01:05:28 2016 +0000
Bug #20899: Dunlib/PulseAudio fixes.
* Try even harder to avoid sticking libraries with the wrong architecture in the container.
* If libpulsecore can't be found, don't bindmount libpulse.so, since it will abort() in the stub. No audio, but it beats crashing. --- .../sandboxed-tor-browser/internal/dynlib/cache.go | 4 +- .../sandboxed-tor-browser/internal/dynlib/ldso.go | 30 +++++++- .../internal/sandbox/application.go | 70 ++++++------------ .../internal/sandbox/pulse.go | 85 +++++++++++++++++++++- 4 files changed, 135 insertions(+), 54 deletions(-)
diff --git a/src/cmd/sandboxed-tor-browser/internal/dynlib/cache.go b/src/cmd/sandboxed-tor-browser/internal/dynlib/cache.go index 4bcc60d..11f1ee3 100644 --- a/src/cmd/sandboxed-tor-browser/internal/dynlib/cache.go +++ b/src/cmd/sandboxed-tor-browser/internal/dynlib/cache.go @@ -94,7 +94,7 @@ func (c *Cache) ResolveLibraries(binaries []string, extraLibs []string, ldLibrar break } for _, fn := range toCheck { - impLibs, err := GetLibraries(fn) + impLibs, err := getLibraries(fn) if err != nil { return nil, err } @@ -373,6 +373,8 @@ func LoadCache() (*Cache, error) { // osVersion, or hwcap. if ourOsVersion < e.osVersion { Debugf("dynlib: ignoring library: %v (osVersion: %x)", e.key, e.osVersion) + } else if err = ValidateLibraryClass(e.value); err != nil { + Debugf("dynlib: ignoring library %v (%v)", e.key, err) } else if flagCheckFn(e.flags) && capCheckFn(e.hwcap) { vec := c.store[e.key] vec = append(vec, e) diff --git a/src/cmd/sandboxed-tor-browser/internal/dynlib/ldso.go b/src/cmd/sandboxed-tor-browser/internal/dynlib/ldso.go index 812a5a6..92e16c1 100644 --- a/src/cmd/sandboxed-tor-browser/internal/dynlib/ldso.go +++ b/src/cmd/sandboxed-tor-browser/internal/dynlib/ldso.go @@ -19,6 +19,7 @@ package dynlib import ( "debug/elf" "errors" + "fmt" "os" "path/filepath" "runtime" @@ -26,9 +27,7 @@ import (
var errUnsupported = errors.New("dynlib: unsupported os/architecture")
-// GetLibraries returns the dynamic libraries imported by the given file at -// dynamic link time. -func GetLibraries(fn string) ([]string, error) { +func getLibraries(fn string) ([]string, error) { f, err := elf.Open(fn) if err != nil { return nil, err @@ -38,6 +37,31 @@ func GetLibraries(fn string) ([]string, error) { return f.ImportedLibraries() }
+// ValidateLibraryClass ensures that the library matches the current +// architecture. +func ValidateLibraryClass(fn string) error { + f, err := elf.Open(fn) + if err != nil { + return err + } + defer f.Close() + + var expectedClass elf.Class + switch runtime.GOARCH { + case "amd64": + expectedClass = elf.ELFCLASS64 + case "386": + expectedClass = elf.ELFCLASS32 + default: + return errUnsupported + } + + if f.Class != expectedClass { + return fmt.Errorf("unsupported class: %v", fn, f.Class) + } + return nil +} + // FindLdSo returns the path to the `ld.so` dynamic linker for the current // architecture, which is usually a symlink func FindLdSo(cache *Cache) (string, string, error) { diff --git a/src/cmd/sandboxed-tor-browser/internal/sandbox/application.go b/src/cmd/sandboxed-tor-browser/internal/sandbox/application.go index 939d805..4cd4ca0 100644 --- a/src/cmd/sandboxed-tor-browser/internal/sandbox/application.go +++ b/src/cmd/sandboxed-tor-browser/internal/sandbox/application.go @@ -28,7 +28,6 @@ import ( "path/filepath" "runtime" "sort" - "strings" "syscall"
"cmd/sandboxed-tor-browser/internal/dynlib" @@ -220,54 +219,13 @@ func RunTorBrowser(cfg *config.Config, manif *config.Manifest, tor *tor.Tor) (cm ldLibraryPath = ldLibraryPath + glLibPaths
if cfg.Sandbox.EnablePulseAudio && pulseAudioWorks { - const libPulse = "libpulse.so.0" - - paLibsPath := findDistributionDependentLibs(nil, "", "pulseaudio") - if paLibsPath != "" && cache.GetLibraryPath(libPulse) != "" { - const restrictedPulseDir = "/usr/lib/pulseaudio" - - // The library search path ("/usr/lib/pulseaudio"), is - // hardcoded into libpulse.so.0, because you suck, and we hate - // you. - extraLibs = append(extraLibs, libPulse) - ldLibraryPath = ldLibraryPath + ":" + paLibsPath - h.dir(restrictedPulseDir) - extraLdLibraryPath = extraLdLibraryPath + ":" + restrictedPulseDir - - boundPulseCore := false - matches, err := filepath.Glob(paLibsPath + "/*.so") - if err != nil { - return nil, err - } - for _, v := range matches { - _, f := filepath.Split(v) - if strings.HasPrefix(f, "libpulsecore") { - boundPulseCore = true - } - h.roBind(v, filepath.Join(restrictedPulseDir, f), false) - extraLibs = append(extraLibs, f) - } - - if !boundPulseCore { - // Debian sticks libpulsecore-blah.so in /usr/lib, unlike - // everyone else who sticks it in /usr/lib/pulseaudo, - // because fuck you. - matches, err = filepath.Glob("/usr/lib/libpulsecore-*.so") - if err != nil { - return nil, err - } - if len(matches) == 0 { - log.Printf("sandbox: Failed to find `libpulsecore-<version>.so`, audio will crash the browser.") - } else { - for _, v := range matches { - _, f := filepath.Split(v) - h.roBind(v, filepath.Join(restrictedPulseDir, f), false) - extraLibs = append(extraLibs, f) - } - } - } + paLibs, paPath, paExtraPath, err := h.appendRestrictedPulseAudio(cache) + if err != nil { + log.Printf("sandbox: Failed to find PulseAudio libraries: %v", err) } else { - log.Printf("sandbox: Failed to find pulse audio libraries.") + extraLibs = append(extraLibs, paLibs...) + ldLibraryPath = ldLibraryPath + paPath + extraLdLibraryPath = extraLdLibraryPath + paExtraPath } } if codec := findBestCodec(cache); codec != "" { @@ -588,7 +546,21 @@ func findDistributionDependentLibs(extraSearch []string, subDir, fn string) stri
for _, base := range searchPaths { candidate := filepath.Join(base, subDir, fn) - if FileExists(candidate) { + if FileExists(candidate) && dynlib.ValidateLibraryClass(candidate) == nil { + return candidate + } + } + return "" +} + +func findDistributionDependentDir(extraSearch []string, subDir, fn string) string { + var searchPaths []string + searchPaths = append(searchPaths, extraSearch...) + searchPaths = append(searchPaths, distributionDependentLibSearchPath...) + + for _, base := range searchPaths { + candidate := filepath.Join(base, subDir, fn) + if DirExists(candidate) { return candidate } } diff --git a/src/cmd/sandboxed-tor-browser/internal/sandbox/pulse.go b/src/cmd/sandboxed-tor-browser/internal/sandbox/pulse.go index 0b2fd6d..c58843b 100644 --- a/src/cmd/sandboxed-tor-browser/internal/sandbox/pulse.go +++ b/src/cmd/sandboxed-tor-browser/internal/sandbox/pulse.go @@ -24,6 +24,9 @@ import ( "strings"
xdg "github.com/cep21/xdgbasedir" + + "cmd/sandboxed-tor-browser/internal/dynlib" + . "cmd/sandboxed-tor-browser/internal/utils" )
func (h *hugbox) enablePulseAudio() error { @@ -48,7 +51,7 @@ func (h *hugbox) enablePulseAudio() error { }
if fi, err := os.Stat(sockPath); err != nil { - // No pulse Audio socket. + // No PulseAudio socket. return fmt.Errorf("sandbox: no PulseAudio socket") } else if fi.Mode()&os.ModeSocket == 0 { // Not an AF_LOCAL socket. @@ -93,3 +96,83 @@ func (h *hugbox) enablePulseAudio() error {
return nil } + +func (h *hugbox) appendRestrictedPulseAudio(cache *dynlib.Cache) ([]string, string, string, error) { + const libPulse = "libpulse.so.0" + + type roBindEnt struct { + src, dst string + } + toRoBind := []roBindEnt{} + + extraLibs := []string{} + ldLibraryPath := "" + extraLdLibraryPath := "" + + paLibsPath := findDistributionDependentDir(nil, "", "pulseaudio") + if paLibsPath != "" && cache.GetLibraryPath(libPulse) != "" { + const restrictedPulseDir = "/usr/lib/pulseaudio" + + // The library search path ("/usr/lib/pulseaudio"), is + // hardcoded into libpulse.so.0, because you suck, and we hate + // you. + + extraLibs = append(extraLibs, libPulse) + ldLibraryPath = ldLibraryPath + ":" + paLibsPath + extraLdLibraryPath = extraLdLibraryPath + ":" + restrictedPulseDir + + // The special handling for libpulsecore is because, we need to dlopen + // it in our stub. + + boundPulseCore := false + matches, err := filepath.Glob(paLibsPath + "/*.so") + if err != nil { + return nil, "", "", err + } + for _, v := range matches { + if dynlib.ValidateLibraryClass(v) != nil { + Debugf("sandbox: Unsuitable PulseAudio so: %v", v) + continue + } + _, f := filepath.Split(v) + if strings.HasPrefix(f, "libpulsecore") { + boundPulseCore = true + } + toRoBind = append(toRoBind, roBindEnt{v, filepath.Join(restrictedPulseDir, f)}) + extraLibs = append(extraLibs, f) + } + + // Debian sticks libpulsecore-blah.so in /usr/lib, unlike + // everyone else who sticks it in /usr/lib/pulseaudo, + // because fuck you. + if !boundPulseCore { + matches, err = filepath.Glob("/usr/lib/libpulsecore-*.so") + if err != nil { + return nil, "", "", err + } + for _, v := range matches { + if dynlib.ValidateLibraryClass(v) != nil { + Debugf("sandbox: Unsuitable pulsecore: %v", v) + continue + } + _, f := filepath.Split(v) + toRoBind = append(toRoBind, roBindEnt{v, filepath.Join(restrictedPulseDir, f)}) + extraLibs = append(extraLibs, f) + boundPulseCore = true + break + } + } + + // Now that we're done trying to find all the PulseAudio bits, + // actually bindmount everything into the sandbox. + if boundPulseCore { + h.dir(restrictedPulseDir) + for _, ent := range toRoBind { + h.roBind(ent.src, ent.dst, false) + } + return extraLibs, ldLibraryPath, extraLdLibraryPath, nil + } + } + + return nil, "", "", fmt.Errorf("failed to find PulseAudio libraries") +}