diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c71aeea0..687b51653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,11 @@ _The old changelog can be found in the `release-2.6` branch_ - e2e test fixes for new kernels, new unsquashfs version. - Show correct web URI for detached builds against alternate remotes. +## New features / functionalities + + - The singularity binary is now relocatable when built without setuid + support + # v3.7.0 - [2020-11-24] diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 40add42bc..45f9bca4a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -21,6 +21,7 @@ - Brian Bockelman - Carl Madison - Cedric Clerget , + - Chris Burr - Chris Hollowell - Christian Neyers - Daniele Tamino diff --git a/internal/pkg/buildcfg/confgen/gen.go b/internal/pkg/buildcfg/confgen/gen.go index d492e4bf3..04ab71faa 100644 --- a/internal/pkg/buildcfg/confgen/gen.go +++ b/internal/pkg/buildcfg/confgen/gen.go @@ -11,6 +11,7 @@ import ( "fmt" "io/ioutil" "os" + "regexp" "strings" "text/template" ) @@ -29,22 +30,89 @@ type Define struct { } // WriteLine writes a line of configuration. -func (d Define) WriteLine() (s string) { - s = "const " + d.Words[1] + " = " + d.Words[2] - +func (d Define) WriteLine(isSuidInstall bool) (s string) { + s = d.Words[2] if len(d.Words) > 3 { + for _, w := range d.Words[3:] { + s += " + " + w + } } - for _, w := range d.Words[3:] { - s += " + " + w + varType := "const" + varStatement := d.Words[1] + " = " + s + + // For security, always mark variables as const when building with SetUID support + if !isSuidInstall { + // Apply runtime relocation to some variables + switch d.Words[1] { + case + "BINDIR", + "LIBEXECDIR", + "SYSCONFDIR", + "SESSIONDIR", + "SINGULARITY_CONFDIR", + "PLUGIN_ROOTDIR": + varType = "var" + varStatement = d.Words[1] + " = RelocatePath(" + s + ")" + default: + // Some variables are defined relative to others and cannot be const + if strings.Contains(s, "SINGULARITY_CONFDIR") { + varType = "var" + } + } } - return s + + return varType + " " + varStatement } var confgenTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. package buildcfg -{{ range $i, $d := . }} -{{$d.WriteLine -}} +{{if not .IsSuidInstall}} +import ( + "os" + "path/filepath" + "strings" +) + +func RelocatePath(original string) (string) { + // For security, never allow relocation when built with SetUID support + if SINGULARITY_SUID_INSTALL != 0 { + panic("This code should not exist when SINGULARITY_SUID_INSTALL is set") + } + + if ! strings.HasPrefix(original, "{{.Prefix}}") { + return original + } + + executablePath, err := os.Executable() + if err != nil { + return original + } + prefix := filepath.Dir(executablePath) + + switch filepath.Base(executablePath) { + case "singularity": + // PREFIX/bin/singularity + prefix = filepath.Dir(prefix) + case "starter": + // PREFIX/libexec/singularity/bin/starter + prefix = filepath.Dir(filepath.Dir(filepath.Dir(prefix))) + default: + return original + } + + relativePath, err := filepath.Rel("{{.Prefix}}", original) + if err != nil { + panic(err) + } + + result := filepath.Join(prefix, relativePath) + return result +} +{{end}} + +{{ range $i, $d := .Defines }} +{{$d.WriteLine $.IsSuidInstall -}} {{end}} `)) @@ -56,6 +124,24 @@ func main() { } defer outFile.Close() + // Determin if this is a setuid install + b, err := ioutil.ReadFile(os.Args[1]) + if err != nil { + fmt.Println(err) + return + } + re := regexp.MustCompile("#define SINGULARITY_SUID_INSTALL ([01])") + isSuidInstall := true + switch re.FindStringSubmatch(string(b))[1] { + case "0": + isSuidInstall = false + case "1": + isSuidInstall = true + default: + panic("Failed to parse value of SINGULARITY_SUID_INSTALL") + } + + // Parse the config.h file inFile, err := ioutil.ReadFile(os.Args[1]) if err != nil { fmt.Println(err) @@ -64,12 +150,22 @@ func main() { header := []Define{} s := bufio.NewScanner(bytes.NewReader(inFile)) + prefix := "" for s.Scan() { d := parseLine(s.Text()) if len(d.Words) > 2 && d.Words[0] == "#define" { + if d.Words[1] == "PREFIX" { + if len(d.Words) != 3 { + panic("Expected PREFIX to contain 3 elements") + } + prefix = d.Words[2] + } header = append(header, d) } } + if prefix == "" { + panic("Failed to find value of PREFIX") + } if goBuildTags := os.Getenv("GO_BUILD_TAGS"); goBuildTags != "" { d := Define{ @@ -82,5 +178,18 @@ func main() { header = append(header, d) } - confgenTemplate.Execute(outFile, header) + data := struct { + Prefix string + Defines []Define + IsSuidInstall bool + }{ + prefix[1 : len(prefix)-1], + header, + isSuidInstall, + } + err = confgenTemplate.Execute(outFile, data) + if err != nil { + fmt.Println(err) + return + } } diff --git a/internal/pkg/plugin/meta.go b/internal/pkg/plugin/meta.go index 079ab3cc0..b22114a57 100644 --- a/internal/pkg/plugin/meta.go +++ b/internal/pkg/plugin/meta.go @@ -20,7 +20,7 @@ import ( "github.com/sylabs/singularity/pkg/sylog" ) -const ( +var ( // rootDir is the root directory for the plugin // installation, typically located within LIBEXECDIR. rootDir = buildcfg.PLUGIN_ROOTDIR