diff --git a/go.mod b/go.mod index 37d2205e999..df9c1a960ab 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,8 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946 github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect + kernel.org/pub/linux/libs/security/libcap/psx v1.2.51 // indirect ) diff --git a/go.sum b/go.sum index 0a340f3fb83..e6d8ccf7067 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946 h1:RRTOwBnwZR4a3IMyPq1uchxJcrLKWF4NTCHB2fbvo5Y= +github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946/go.mod h1:wjznJ04q4Tvsbx3vkzfmgfEOe6w5dSGlXFa+xbSl9X8= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= @@ -86,6 +88,7 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -102,3 +105,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.51 h1:VXVXjnTUsA9zeHIolNb6moSXZavDe1pD8Q0lPXZEOwc= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.51/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go index 22fe0f9b4c1..52f10158411 100644 --- a/libcontainer/configs/config.go +++ b/libcontainer/configs/config.go @@ -10,6 +10,7 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/sys/unix" + "github.com/landlock-lsm/go-landlock/landlock" "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runtime-spec/specs-go" ) @@ -85,6 +86,33 @@ type Syscall struct { Args []*Arg `json:"args"` } +// Landlock specifies the Landlock unprivileged access control settings for the container process. +type Landlock struct { + Ruleset *Ruleset `json:"ruleset"` + Rules *Rules `json:"rules"` + DisableBestEffort bool `json:"disableBestEffort"` +} + +// Ruleset identifies a set of rules (i.e., actions on objects) that need to be handled in Landlock. +type Ruleset struct { + HandledAccessFS landlock.AccessFSSet `json:"handledAccessFS"` +} + +// Rules represents the security policies (i.e., actions allowed on objects) in Landlock. +type Rules struct { + PathBeneath []*RulePathBeneath `json:"pathBeneath"` +} + +// RulePathBeneath defines the file-hierarchy typed rule that grants the access rights specified by +// AllowedAccess to the file hierarchies under the given Paths in Landlock. +type RulePathBeneath struct { + AllowedAccess landlock.AccessFSSet `json:"allowedAccess"` + Paths []string `json:"paths"` +} + +// TODO Windows. Many of these fields should be factored out into those parts +// which are common across platforms, and those which are platform specific. + // Config defines configuration options for executing a process inside a contained environment. type Config struct { // NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs @@ -225,6 +253,9 @@ type Config struct { // IOPriority is the container's I/O priority. IOPriority *IOPriority `json:"io_priority,omitempty"` + // Landlock specifies the Landlock unprivileged access control settings for the container process. + // NoNewPrivileges must be enabled to use Landlock. + Landlock *Landlock `json:"landlock,omitempty"` } // Scheduler is based on the Linux sched_setattr(2) syscall. diff --git a/libcontainer/landlock/config.go b/libcontainer/landlock/config.go new file mode 100644 index 00000000000..b2b4302b0ec --- /dev/null +++ b/libcontainer/landlock/config.go @@ -0,0 +1,35 @@ +package landlock + +import ( + "fmt" + + "github.com/landlock-lsm/go-landlock/landlock" + ll "github.com/landlock-lsm/go-landlock/landlock/syscall" +) + +var accessFSSets = map[string]landlock.AccessFSSet{ + "execute": ll.AccessFSExecute, + "write_file": ll.AccessFSWriteFile, + "read_file": ll.AccessFSReadFile, + "read_dir": ll.AccessFSReadDir, + "remove_dir": ll.AccessFSRemoveDir, + "remove_file": ll.AccessFSRemoveFile, + "make_char": ll.AccessFSMakeChar, + "make_dir": ll.AccessFSMakeDir, + "make_reg": ll.AccessFSMakeReg, + "make_sock": ll.AccessFSMakeSock, + "make_fifo": ll.AccessFSMakeFifo, + "make_block": ll.AccessFSMakeBlock, + "make_sym": ll.AccessFSMakeSym, +} + +// ConvertStringToAccessFSSet converts a string into a go-landlock AccessFSSet +// access right. +// This gives more explicit control over the mapping between the permitted +// values in the spec and the ones supported in go-landlock library. +func ConvertStringToAccessFSSet(in string) (landlock.AccessFSSet, error) { + if access, ok := accessFSSets[in]; ok { + return access, nil + } + return 0, fmt.Errorf("string %s is not a valid access right for landlock", in) +} diff --git a/libcontainer/landlock/landlock.go b/libcontainer/landlock/landlock.go new file mode 100644 index 00000000000..52b7aa49a02 --- /dev/null +++ b/libcontainer/landlock/landlock.go @@ -0,0 +1,58 @@ +package landlock + +import ( + "errors" + "fmt" + + "github.com/landlock-lsm/go-landlock/landlock" + + "github.com/opencontainers/runc/libcontainer/configs" +) + +// Initialize Landlock unprivileged access control for the container process +// based on the given settings. +// The specified `ruleset` identifies a set of rules (i.e., actions on objects) +// that need to be handled (i.e., restricted) by Landlock. And if no `rule` +// explicitly allow them, they should then be forbidden. +// The `disableBestEffort` input gives control over whether the best-effort +// security approach should be applied for Landlock access rights. +func InitLandlock(config *configs.Landlock) error { + if config == nil { + return errors.New("cannot initialize Landlock - nil config passed") + } + + ruleset := config.Ruleset.HandledAccessFS + llConfig, err := landlock.NewConfig(ruleset) + if err != nil { + return fmt.Errorf("could not create ruleset: %w", err) + } + + if !config.DisableBestEffort { + *llConfig = llConfig.BestEffort() + } + + if err := llConfig.RestrictPaths( + pathAccesses(config.Rules)..., + ); err != nil { + return fmt.Errorf("could not restrict paths: %w", err) + } + + return nil +} + +// Convert Libcontainer RulePathBeneath to go-landlock PathOpt. +func pathAccess(rule *configs.RulePathBeneath) landlock.PathOpt { + return landlock.PathAccess(rule.AllowedAccess, rule.Paths...) +} + +// Convert Libcontainer Rules to an array of go-landlock PathOpt. +func pathAccesses(rules *configs.Rules) []landlock.PathOpt { + pathAccesses := []landlock.PathOpt{} + + for _, rule := range rules.PathBeneath { + opt := pathAccess(rule) + pathAccesses = append(pathAccesses, opt) + } + + return pathAccesses +} diff --git a/libcontainer/setns_init_linux.go b/libcontainer/setns_init_linux.go index 92c6ef77030..713f6c2043c 100644 --- a/libcontainer/setns_init_linux.go +++ b/libcontainer/setns_init_linux.go @@ -12,6 +12,7 @@ import ( "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/keys" + "github.com/opencontainers/runc/libcontainer/landlock" "github.com/opencontainers/runc/libcontainer/seccomp" "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/utils" @@ -116,6 +117,12 @@ func (l *linuxSetnsInit) Init() error { if err != nil { return err } + // `noNewPrivileges` must be enabled to use Landlock. + if l.config.Config.Landlock != nil && l.config.NoNewPrivileges { + if err := landlock.InitLandlock(l.config.Config.Landlock); err != nil { + return fmt.Errorf("unable to init Landlock: %w", err) + } + } // Set seccomp as close to execve as possible, so as few syscalls take // place afterward (reducing the amount of syscalls that users need to // enable in their seccomp profiles). diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go index 79a9a790049..5b24491fff9 100644 --- a/libcontainer/specconv/spec_linux.go +++ b/libcontainer/specconv/spec_linux.go @@ -18,6 +18,7 @@ import ( "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/internal/userns" + "github.com/opencontainers/runc/libcontainer/landlock" "github.com/opencontainers/runc/libcontainer/seccomp" libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" "github.com/opencontainers/runtime-spec/specs-go" @@ -556,6 +557,19 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) { ioPriority := *spec.Process.IOPriority config.IOPriority = &ioPriority } + if spec.Process.Landlock != nil { + landlock, err := SetupLandlock(spec.Process.Landlock) + if err != nil { + return nil, err + } + config.Landlock = landlock + } + + landlock, err := SetupLandlock(spec.Process.Landlock) + if err != nil { + return nil, err + } + config.Landlock = landlock } createHooks(spec, config) config.Version = specs.Version @@ -1135,6 +1149,55 @@ func parseMountOptions(options []string) *configs.Mount { return &m } +func SetupLandlock(ll *specs.Landlock) (*configs.Landlock, error) { + if ll == nil { + return nil, nil + } + + // No ruleset specified, assume landlock disabled. + if ll.Ruleset == nil || len(ll.Ruleset.HandledAccessFS) == 0 { + return nil, nil + } + + newConfig := &configs.Landlock{ + Ruleset: new(configs.Ruleset), + Rules: &configs.Rules{ + PathBeneath: []*configs.RulePathBeneath{}, + }, + DisableBestEffort: ll.DisableBestEffort, + } + + for _, access := range ll.Ruleset.HandledAccessFS { + newAccessFS, err := landlock.ConvertStringToAccessFSSet(string(access)) + if err != nil { + return nil, err + } + newConfig.Ruleset.HandledAccessFS |= newAccessFS + } + + // Loop through all Landlock path beneath rule blocks and convert them to libcontainer format. + for _, rulePath := range ll.Rules.PathBeneath { + if len(rulePath.AllowedAccess) > 0 { + newRule := configs.RulePathBeneath{ + AllowedAccess: 0, + Paths: rulePath.Paths, + } + + for _, access := range rulePath.AllowedAccess { + newAllowedAccess, err := landlock.ConvertStringToAccessFSSet(string(access)) + if err != nil { + return nil, err + } + newRule.AllowedAccess |= newAllowedAccess + } + + newConfig.Rules.PathBeneath = append(newConfig.Rules.PathBeneath, &newRule) + } + } + + return newConfig, nil +} + func SetupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) { if config == nil { return nil, nil diff --git a/libcontainer/specconv/spec_linux_test.go b/libcontainer/specconv/spec_linux_test.go index 8c7fb774f97..b5812c0ff5b 100644 --- a/libcontainer/specconv/spec_linux_test.go +++ b/libcontainer/specconv/spec_linux_test.go @@ -2,10 +2,12 @@ package specconv import ( "os" + "reflect" "strings" "testing" dbus "github.com/godbus/dbus/v5" + ll "github.com/landlock-lsm/go-landlock/landlock" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs/validate" "github.com/opencontainers/runc/libcontainer/devices" @@ -185,6 +187,107 @@ func TestSetupSeccompWrongArchitecture(t *testing.T) { } } +func TestSetupLandlock(t *testing.T) { + conf := &specs.Landlock{ + Ruleset: &specs.LandlockRuleset{ + HandledAccessFS: []specs.LandlockFSAction{ + specs.FSActExecute, + specs.FSActWriteFile, + specs.FSActReadFile, + specs.FSActReadDir, + specs.FSActRemoveDir, + specs.FSActRemoveFile, + specs.FSActMakeChar, + specs.FSActMakeDir, + specs.FSActMakeReg, + specs.FSActMakeSock, + specs.FSActMakeFifo, + specs.FSActMakeBlock, + specs.FSActMakeSym, + }, + }, + Rules: &specs.LandlockRules{ + PathBeneath: []specs.LandlockRulePathBeneath{ + { + AllowedAccess: []specs.LandlockFSAction{ + specs.FSActExecute, + specs.FSActReadFile, + specs.FSActReadDir, + }, + Paths: []string{ + "/usr", + "/bin", + }, + }, + { + AllowedAccess: []specs.LandlockFSAction{ + specs.FSActExecute, + specs.FSActWriteFile, + specs.FSActReadFile, + specs.FSActRemoveFile, + specs.FSActMakeChar, + specs.FSActMakeReg, + specs.FSActMakeSock, + specs.FSActMakeFifo, + specs.FSActMakeBlock, + specs.FSActMakeSym, + }, + Paths: []string{ + "/tmp", + }, + }, + }, + }, + DisableBestEffort: false, + } + + landlock, err := SetupLandlock(conf) + if err != nil { + t.Errorf("Couldn't create Landlock config: %v", err) + } + + // Execute | WriteFile | ReadFile | ReadDir | RemoveDir | RemoveFile | MakeChar | + // MakeDir | MakeReg | MakeSock | MakeFifo | MakeBlock | MakeSym + expectedRulesetAccess := ll.AccessFSSet(0x1FFF) + ruleset := landlock.Ruleset + if ruleset.HandledAccessFS != expectedRulesetAccess { + t.Errorf("Expected ruleset not found, expected %v, got: %v", + expectedRulesetAccess, ruleset.HandledAccessFS) + } + + pathRules := landlock.Rules.PathBeneath + + pathRulesLength := len(pathRules) + if pathRulesLength != 2 { + t.Errorf("Expected 2 path beneath rules, got :%d", pathRulesLength) + } + + expectedPathRulesAccess := []configs.RulePathBeneath{ + { + // Execute | ReadFile | ReadDir + AllowedAccess: 0xD, + Paths: []string{"/usr", "/bin"}, + }, + { + // Execute | WriteFile | ReadFile | RemoveFile | MakeChar | MakeReg | MakeSock | MakeFifo | + // MakeBlock | MakeSym + AllowedAccess: 0x1F67, + Paths: []string{"/tmp"}, + }, + } + + for i, rule := range pathRules { + if !reflect.DeepEqual(*rule, expectedPathRulesAccess[i]) { + t.Errorf("Wrong rule conversion for the rule %d under test, expected %v, got: %v", + i, expectedPathRulesAccess[i], rule) + } + } + + if landlock.DisableBestEffort { + t.Error("Wrong conversion for DisableBestEffort") + } +} + func TestSetupSeccomp(t *testing.T) { errnoRet := uint(55) conf := &specs.LinuxSeccomp{ diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go index 9f7fa45d533..b3b8f986424 100644 --- a/libcontainer/standard_init_linux.go +++ b/libcontainer/standard_init_linux.go @@ -14,6 +14,7 @@ import ( "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/keys" + "github.com/opencontainers/runc/libcontainer/landlock" "github.com/opencontainers/runc/libcontainer/seccomp" "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/utils" @@ -267,6 +268,13 @@ func (l *linuxStandardInit) Init() error { // https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1290-L1318 _ = l.fifoFile.Close() + // NoNewPrivileges must be enabled to use Landlock. + if l.config.Config.Landlock != nil && l.config.NoNewPrivileges { + if err := landlock.InitLandlock(l.config.Config.Landlock); err != nil { + return fmt.Errorf("unable to init Landlock: %w", err) + } + } + s := l.config.SpecState s.Pid = unix.Getpid() s.Status = specs.StateCreated diff --git a/vendor/github.com/landlock-lsm/go-landlock/LICENSE b/vendor/github.com/landlock-lsm/go-landlock/LICENSE new file mode 100644 index 00000000000..aaa7810eb0d --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Günther Noack + +PERMISSION is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/abi_versions.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/abi_versions.go new file mode 100644 index 00000000000..f53ad7dc4e7 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/abi_versions.go @@ -0,0 +1,35 @@ +package landlock + +import ll "github.com/landlock-lsm/go-landlock/landlock/syscall" + +type abiInfo struct { + version int + supportedAccessFS AccessFSSet +} + +var abiInfos = []abiInfo{ + { + version: 0, + supportedAccessFS: 0, + }, + { + version: 1, + supportedAccessFS: (1 << 13) - 1, + }, +} + +// getSupportedABIVersion returns the kernel-supported ABI version. +// +// If the ABI version supported by the kernel is higher than the +// newest one known to go-landlock, the highest ABI version known to +// go-landlock is returned. +func getSupportedABIVersion() abiInfo { + v, err := ll.LandlockGetABIVersion() + if err != nil { + v = 0 // ABI version 0 is "no Landlock support". + } + if v >= len(abiInfos) { + v = len(abiInfos) - 1 + } + return abiInfos[v] +} diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/accessfs.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/accessfs.go new file mode 100644 index 00000000000..41f08022dd3 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/accessfs.go @@ -0,0 +1,68 @@ +package landlock + +import ( + "fmt" + "strings" +) + +var flagNames = []string{ + "Execute", + "WriteFile", + "ReadFile", + "ReadDir", + "RemoveDir", + "RemoveFile", + "MakeChar", + "MakeDir", + "MakeReg", + "MakeSock", + "MakeFifo", + "MakeBlock", + "MakeSym", +} + +// AccessFSSet is a set of Landlockable file system access operations. +type AccessFSSet uint64 + +var supportedAccessFS = AccessFSSet((1 << 13) - 1) + +func (a AccessFSSet) String() string { + if a.isEmpty() { + return "∅" + } + var b strings.Builder + b.WriteByte('{') + for i := 0; i < 64; i++ { + if a&(1< 1 { + b.WriteByte(',') + } + if i < len(flagNames) { + b.WriteString(flagNames[i]) + } else { + b.WriteString(fmt.Sprintf("1<<%v", i)) + } + } + b.WriteByte('}') + return b.String() +} + +func (a AccessFSSet) isSubset(b AccessFSSet) bool { + return a&b == a +} + +func (a AccessFSSet) intersect(b AccessFSSet) AccessFSSet { + return a & b +} + +func (a AccessFSSet) isEmpty() bool { + return a == 0 +} + +// valid returns true iff the given AccessFSSet is supported by this +// version of go-landlock. +func (a AccessFSSet) valid() bool { + return a.isSubset(supportedAccessFS) +} diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/config.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/config.go new file mode 100644 index 00000000000..af4187de519 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/config.go @@ -0,0 +1,273 @@ +package landlock + +import ( + "errors" + "fmt" + + ll "github.com/landlock-lsm/go-landlock/landlock/syscall" +) + +// Access permission sets for filesystem access. +const ( + // The set of access rights that only apply to files. + accessFile AccessFSSet = ll.AccessFSExecute | ll.AccessFSWriteFile | ll.AccessFSReadFile + + // The set of access rights associated with read access to files and directories. + accessFSRead AccessFSSet = ll.AccessFSExecute | ll.AccessFSReadFile | ll.AccessFSReadDir + + // The set of access rights associated with write access to files and directories. + accessFSWrite AccessFSSet = ll.AccessFSWriteFile | ll.AccessFSRemoveDir | ll.AccessFSRemoveFile | ll.AccessFSMakeChar | ll.AccessFSMakeDir | ll.AccessFSMakeReg | ll.AccessFSMakeSock | ll.AccessFSMakeFifo | ll.AccessFSMakeBlock | ll.AccessFSMakeSym + + // The set of access rights associated with read and write access to files and directories. + accessFSReadWrite AccessFSSet = accessFSRead | accessFSWrite +) + +// These are Landlock configurations for the currently supported +// Landlock ABI versions, configured to restrict the highest possible +// set of operations possible for each version. +// +// The higher the ABI version, the more operations Landlock will be +// able to restrict. +var ( + // Landlock V1 support (basic file operations). + V1 = Config{ + handledAccessFS: abiInfos[1].supportedAccessFS, + } +) + +// The Landlock configuration describes the desired set of +// landlockable operations to be restricted and the constraints on it +// (e.g. best effort mode). +type Config struct { + handledAccessFS AccessFSSet + bestEffort bool +} + +// NewConfig creates a new Landlock configuration with the given parameters. +// +// Passing an AccessFSSet will set that as the set of file system +// operations to restrict when enabling Landlock. The AccessFSSet +// needs to stay within the bounds of what go-landlock supports. +func NewConfig(args ...interface{}) (*Config, error) { + // Implementation note: This factory is written with future + // extensibility in mind. Only specific types are supported as + // input, but in the future more might be added. + // + // This constructor ensures that callers can't construct + // invalid Config values. + var c Config + for _, arg := range args { + if afs, ok := arg.(AccessFSSet); ok { + if !c.handledAccessFS.isEmpty() { + return nil, errors.New("only one AccessFSSet may be provided") + } + if !afs.valid() { + return nil, errors.New("unsupported AccessFSSet value; upgrade go-landlock?") + } + c.handledAccessFS = afs + } else { + return nil, fmt.Errorf("unknown argument %v; only AccessFSSet-type argument is supported", arg) + } + } + return &c, nil +} + +// MustConfig is like NewConfig but panics on error. +func MustConfig(args ...interface{}) Config { + c, err := NewConfig(args...) + if err != nil { + panic(err) + } + return *c +} + +// String builds a human-readable representation of the Config. +func (c Config) String() string { + abi := abiInfo{version: -1} // invalid + for _, a := range abiInfos { + if c.handledAccessFS.isSubset(a.supportedAccessFS) { + abi = a + } + } + + var desc = c.handledAccessFS.String() + if abi.supportedAccessFS == c.handledAccessFS { + desc = "all" + } + + var bestEffort = "" + if c.bestEffort { + bestEffort = " (best effort)" + } + + var version string + if abi.version < 0 { + version = "V???" + } else { + version = fmt.Sprintf("V%v", abi.version) + } + + return fmt.Sprintf("{Landlock %v; HandledAccessFS: %v%v}", version, desc, bestEffort) +} + +// BestEffort returns a config that will opportunistically enforce +// the strongest rules it can, up to the given ABI version, working +// with the level of Landlock support available in the running kernel. +// +// Warning: A best-effort call to RestrictPaths() will succeed without +// error even when Landlock is not available at all on the current kernel. +func (c Config) BestEffort() Config { + cfg := c + cfg.bestEffort = true + return cfg +} + +// PathOpt is an option value for RestrictPaths(). +type PathOpt struct { + accessFS AccessFSSet + paths []string +} + +// PathAccess is a RestrictPaths() option that grants the access right +// specified by accessFS to the file hierarchies under the given paths. +// +// When accessFS is larger than what is permitted by the Landlock +// version in use, only the applicable subset of accessFS will be used. +// +// Most users should use the functions RODirs, RWDirs, ROFiles and +// RWFiles instead, which provide canned options for commonly used +// values of accessFS. +// +// Filesystem access rights are represented using bits in a uint64. +// The individual access rights and their meaning are defined in the +// landlock/syscall package and explained further in the kernel +// documentation at +// https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights +func PathAccess(accessFS AccessFSSet, paths ...string) PathOpt { + return PathOpt{ + accessFS: accessFS, + paths: paths, + } +} + +// RODirs is a RestrictPaths() option that grants common read-only +// access to files and directories and permits executing files. +func RODirs(paths ...string) PathOpt { return PathAccess(accessFSRead, paths...) } + +// RWDirs is a RestrictPaths() option that grants full (read and +// write) access to files and directories under the given paths. +func RWDirs(paths ...string) PathOpt { return PathAccess(accessFSReadWrite, paths...) } + +// ROFiles is a RestrictPaths() option that grants common read access +// to individual files, but not to directories, for the file +// hierarchies under the given paths. +func ROFiles(paths ...string) PathOpt { return PathAccess(accessFSRead&accessFile, paths...) } + +// RWFiles is a RestrictPaths() option that grants common read and +// write access to files under the given paths, but it does not permit +// access to directories. +func RWFiles(paths ...string) PathOpt { return PathAccess(accessFSReadWrite&accessFile, paths...) } + +// RestrictPaths restricts all goroutines to only "see" the files +// provided as inputs. After this call successfully returns, the +// goroutines will only be able to use files in the ways as they were +// specified in advance in the call to RestrictPaths. +// +// Example: The following invocation will restrict all goroutines so +// that it can only read from /usr, /bin and /tmp, and only write to +// /tmp: +// +// err := landlock.V1.RestrictPaths( +// landlock.RODirs("/usr", "/bin"), +// landlock.RWDirs("/tmp"), +// ) +// if err != nil { +// log.Fatalf("landlock.V1.RestrictPaths(): %v", err) +// } +// +// RestrictPaths returns an error if any of the given paths does not +// denote an actual directory or file, or if Landlock can't be enforced +// using the desired ABI version constraints. +// +// RestrictPaths also sets the "no new privileges" flag for all OS +// threads managed by the Go runtime. +// +// Restrictable access rights +// +// The notions of what "reading" and "writing" mean are limited by what +// the selected Landlock version supports. +// +// Calling RestrictPaths() with a given Landlock ABI version will +// inhibit all future calls to the access rights supported by this ABI +// version, unless the accessed path is in a file hierarchy that is +// specifically allow-listed for a specific set of access rights. +// +// The overall set of operations that RestrictPaths can restrict are: +// +// For reading: +// +// • Executing a file (V1+) +// +// • Opening a file with read access (V1+) +// +// • Opening a directory or listing its content (V1+) +// +// +// For writing: +// +// • Opening a file with write access (V1+) +// +// +// For directory manipulation: +// +// • Removing an empty directory or renaming one (V1+) +// +// • Removing (or renaming) a file (V1+) +// +// • Creating (or renaming or linking) a character device (V1+) +// +// • Creating (or renaming) a directory (V1+) +// +// • Creating (or renaming or linking) a regular file (V1+) +// +// • Creating (or renaming or linking) a UNIX domain socket (V1+) +// +// • Creating (or renaming or linking) a named pipe (V1+) +// +// • Creating (or renaming or linking) a block device (V1+) +// +// • Creating (or renaming or linking) a symbolic link (V1+) +// +// Future versions of Landlock will be able to inhibit more operations. +// Quoting the Landlock documentation: +// +// It is currently not possible to restrict some file-related +// actions accessible through these syscall families: chdir(2), +// truncate(2), stat(2), flock(2), chmod(2), chown(2), setxattr(2), +// utime(2), ioctl(2), fcntl(2), access(2). Future Landlock +// evolutions will enable to restrict them. +// +// The access rights are documented in more depth at: +// https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights +// +// Helper functions for selecting access rights +// +// These helper functions help selecting common subsets of access rights: +// +// • RODirs() selects access rights in the group "for reading". +// In V1, this means reading files, listing directories and executing files. +// +// • RWDirs() selects access rights in the group "for reading", "for writing" and +// "for directory manipulation". In V1, this grants the full set of access rights. +// +// • ROFiles() is like RODirs(), but does not select directory-specific access rights. +// In V1, this means reading and executing files. +// +// • RWFiles() is like RWDirs(), but does not select directory-specific access rights. +// In V1, this means reading, writing and executing files. +// +// The PathAccess() option lets callers define custom subsets of these +// access rights. +func (c Config) RestrictPaths(opts ...PathOpt) error { + return restrictPaths(c, opts...) +} diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/landlock.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/landlock.go new file mode 100644 index 00000000000..9dfa7f6e490 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/landlock.go @@ -0,0 +1,53 @@ +// Package landlock restricts a Go program's ability to use files. +// +// The following invocation will restrict all goroutines so that they +// can only read from /usr, /bin and /tmp, and only write to /tmp: +// +// err := landlock.V1.BestEffort().RestrictPaths( +// landlock.RODirs("/usr", "/bin"), +// landlock.RWDirs("/tmp"), +// ) +// +// This will restrict file access using Landlock V1, if available. If +// unavailable, it will attempt using earlier Landlock versions than +// the one requested. If no Landlock version is available, it will +// still succeed, without restricting file accesses. +// +// More possible invocations +// +// landlock.V1.RestrictPaths(...) enforces the given rules using the +// capabilities of Landlock V1, but returns an error if that is not +// available. +// +// Landlock ABI versioning +// +// Callers need to identify at which ABI level they want to use +// Landlock and call RestrictPaths on the corresponding ABI constant. +// Currently the only available ABI variant is V1, which restricts +// basic filesystem operations. +// +// When new Landlock versions become available in landlock, users +// will need to upgrade their usages manually to higher Landlock +// versions, as there is a risk that new Landlock versions will break +// operations that their programs rely on. +// +// Graceful degradation on older kernels +// +// Programs that get run on different kernel versions will want to use +// the Config.BestEffort() method to gracefully degrade to using the +// best available Landlock version on the current kernel. +// +// Caveats +// +// This warning only applies to programs using cgo and linking C +// libraries that start OS threads through means other than +// pthread_create() before landlock is called: +// +// When using cgo, the landlock package relies on libpsx in order to +// apply the rules across all OS threads, (rather than just the ones +// managed by the Go runtime). psx achieves this by wrapping the +// C-level phtread_create() API which is very commonly used on Unix to +// start threads. However, C libraries calling clone(2) through other +// means before landlock is called might still create threads that +// won't have Landlock protections. +package landlock diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/restrict.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/restrict.go new file mode 100644 index 00000000000..247509a8667 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/restrict.go @@ -0,0 +1,107 @@ +package landlock + +import ( + "errors" + "fmt" + "syscall" + + ll "github.com/landlock-lsm/go-landlock/landlock/syscall" + "golang.org/x/sys/unix" +) + +// The actual restrictPaths implementation. +func restrictPaths(c Config, opts ...PathOpt) error { + handledAccessFS := c.handledAccessFS + abi := getSupportedABIVersion() + if c.bestEffort { + handledAccessFS = handledAccessFS.intersect(abi.supportedAccessFS) + } else { + if !handledAccessFS.isSubset(abi.supportedAccessFS) { + return fmt.Errorf("missing kernel Landlock support. Got Landlock ABI v%v, wanted %v", abi.version, c.String()) + } + } + if handledAccessFS.isEmpty() { + return nil // Success: Nothing to restrict. + } + + rulesetAttr := ll.RulesetAttr{ + HandledAccessFS: uint64(handledAccessFS), + } + fd, err := ll.LandlockCreateRuleset(&rulesetAttr, 0) + if err != nil { + if errors.Is(err, syscall.ENOSYS) || errors.Is(err, syscall.EOPNOTSUPP) { + err = errors.New("landlock is not supported by kernel or not enabled at boot time") + } + if errors.Is(err, syscall.EINVAL) { + err = errors.New("unknown flags, unknown access, or too small size") + } + // Bug, because these should have been caught up front with the ABI version check. + return bug(fmt.Errorf("landlock_create_ruleset: %w", err)) + } + defer syscall.Close(fd) + + for _, opt := range opts { + accessFS := opt.accessFS.intersect(handledAccessFS) + if err := populateRuleset(fd, opt.paths, accessFS); err != nil { + return err + } + } + + if err := ll.AllThreadsPrctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil { + // This prctl invocation should always work. + return bug(fmt.Errorf("prctl(PR_SET_NO_NEW_PRIVS): %v", err)) + } + + if err := ll.AllThreadsLandlockRestrictSelf(fd, 0); err != nil { + if errors.Is(err, syscall.E2BIG) { + // Other errors than E2BIG should never happen. + return fmt.Errorf("the maximum number of stacked rulesets is reached for the current thread: %w", err) + } + return bug(fmt.Errorf("landlock_restrict_self: %w", err)) + } + return nil +} + +func populateRuleset(rulesetFd int, paths []string, access AccessFSSet) error { + for _, p := range paths { + if err := populate(rulesetFd, p, access); err != nil { + return fmt.Errorf("populating ruleset for %q with access %v: %w", p, access, err) + } + } + return nil +} + +func populate(rulesetFd int, path string, access AccessFSSet) error { + fd, err := syscall.Open(path, unix.O_PATH|unix.O_CLOEXEC, 0) + if err != nil { + return fmt.Errorf("open: %w", err) + } + defer syscall.Close(fd) + + pathBeneath := ll.PathBeneathAttr{ + ParentFd: fd, + AllowedAccess: uint64(access), + } + err = ll.LandlockAddPathBeneathRule(rulesetFd, &pathBeneath, 0) + if err != nil { + if errors.Is(err, syscall.EINVAL) { + // The ruleset access permissions must be a superset of the ones we restrict to. + // This should never happen because the call to populate() ensures that. + err = bug(fmt.Errorf("invalid flags, or inconsistent access in the rule: %w", err)) + } else if errors.Is(err, syscall.ENOMSG) && access == 0 { + err = fmt.Errorf("empty access rights: %w", err) + } else { + // Other errors should never happen. + err = bug(err) + } + return fmt.Errorf("landlock_add_rule: %w", err) + } + return nil +} + +// Denotes an error that should not have happened. +// If such an error occurs anyway, please try upgrading the library +// and file a bug to github.com/landlock-lsm/go-landlock if the issue persists. +func bug(err error) error { + return fmt.Errorf("BUG(go-landlock): This should not have happened: %w", err) +} diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/syscall/landlock.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/syscall/landlock.go new file mode 100644 index 00000000000..94417e486a7 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/syscall/landlock.go @@ -0,0 +1,128 @@ +// Package syscall provides a low-level interface to the Linux Landlock sandboxing feature. +// +// The package contains constants and syscall wrappers. The syscall +// wrappers whose names start with AllThreads will execute the syscall +// on all OS threads belonging to the current process, as long as +// these threads have been started implicitly by the Go runtime or +// using `pthread_create`. +// +// This package package is a stopgap solution while there is no +// Landlock support in x/sys/unix. The syscall package is considered +// highly unstable and may change or disappear without warning. +// +// The full documentation can be found at +// https://www.kernel.org/doc/html/latest/userspace-api/landlock.html. +package syscall + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/unix" + "kernel.org/pub/linux/libs/security/libcap/psx" +) + +// Landlock access rights, for use in "access" bit fields. +// +// Please see the full documentation at +// https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights. +const ( + AccessFSExecute = (1 << 0) + AccessFSWriteFile = (1 << 1) + AccessFSReadFile = (1 << 2) + AccessFSReadDir = (1 << 3) + AccessFSRemoveDir = (1 << 4) + AccessFSRemoveFile = (1 << 5) + AccessFSMakeChar = (1 << 6) + AccessFSMakeDir = (1 << 7) + AccessFSMakeReg = (1 << 8) + AccessFSMakeSock = (1 << 9) + AccessFSMakeFifo = (1 << 10) + AccessFSMakeBlock = (1 << 11) + AccessFSMakeSym = (1 << 12) +) + +// RulesetAttr is the Landlock ruleset definition. +// +// Argument of LandlockCreateRuleset(). This structure can grow in future versions of Landlock. +// +// C version is in usr/include/linux/landlock.h +type RulesetAttr struct { + HandledAccessFS uint64 +} + +// The size of the RulesetAttr struct in bytes. +const rulesetAttrSize = 8 + +// LandlockCreateRuleset creates a ruleset file descriptor with the +// given attributes. +func LandlockCreateRuleset(attr *RulesetAttr, flags int) (fd int, err error) { + r0, _, e1 := syscall.Syscall(unix.SYS_LANDLOCK_CREATE_RULESET, uintptr(unsafe.Pointer(attr)), uintptr(rulesetAttrSize), uintptr(flags)) + fd = int(r0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +// LandlockGetABIVersion returns the supported Landlock ABI version (starting at 1). +func LandlockGetABIVersion() (version int, err error) { + const LANDLOCK_CREATE_RULESET_VERSION = 1 << 0 + r0, _, e1 := syscall.Syscall(unix.SYS_LANDLOCK_CREATE_RULESET, 0, 0, LANDLOCK_CREATE_RULESET_VERSION) + version = int(r0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +// There is currently only one Landlock rule type. +const RuleTypePathBeneath = 1 + +// PathBeneathAttr references a file hierarchy and defines the desired +// extent to which it should be usable when the rule is enforced. +type PathBeneathAttr struct { + // AllowedAccess is a bitmask of allowed actions for this file + // hierarchy (cf. "Filesystem flags"). The enabled bits must + // be a subset of the bits defined in the ruleset. + AllowedAccess uint64 + + // ParentFd is a file descriptor, open with `O_PATH`, which identifies + // the parent directory of a file hierarchy, or just a file. + ParentFd int +} + +// LandlockAddPathBeneathRule adds a rule of type "path beneath" to +// the given ruleset fd. attr defines the rule parameters. flags must +// currently be 0. +func LandlockAddPathBeneathRule(rulesetFd int, attr *PathBeneathAttr, flags int) error { + return LandlockAddRule(rulesetFd, RuleTypePathBeneath, unsafe.Pointer(attr), flags) +} + +// LandlockAddRule is the generic landlock_add_rule syscall. +func LandlockAddRule(rulesetFd int, ruleType int, ruleAttr unsafe.Pointer, flags int) (err error) { + _, _, e1 := syscall.Syscall6(unix.SYS_LANDLOCK_ADD_RULE, uintptr(rulesetFd), uintptr(ruleType), uintptr(ruleAttr), uintptr(flags), 0, 0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +// AllThreadsLandlockRestrictSelf enforces the given ruleset on all OS +// threads belonging to the current process. +func AllThreadsLandlockRestrictSelf(rulesetFd int, flags int) (err error) { + _, _, e1 := psx.Syscall3(unix.SYS_LANDLOCK_RESTRICT_SELF, uintptr(rulesetFd), uintptr(flags), 0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +// AllThreadsPrctl is like unix.Prctl, but gets applied on all OS threads at the same time. +func AllThreadsPrctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (err error) { + _, _, e1 := psx.Syscall6(syscall.SYS_PRCTL, uintptr(option), uintptr(arg2), uintptr(arg3), uintptr(arg4), uintptr(arg5), 0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} diff --git a/vendor/kernel.org/pub/linux/libs/security/libcap/psx/License b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/License new file mode 100644 index 00000000000..2645a87d4c7 --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/License @@ -0,0 +1,396 @@ +Unless otherwise *explicitly* stated, the following text describes the +licensed conditions under which the contents of this libcap/psx release +may be used and distributed. + +The licensed conditions are one or the other of these two Licenses: + + - BSD 3-clause + - GPL v2.0 + +------------------------------------------------------------------------- +BSD 3-clause: +------------- + +Redistribution and use in source and binary forms of libcap/psx, with +or without modification, are permitted provided that the following +conditions are met: + +1. Redistributions of source code must retain any existing copyright + notice, and this entire permission notice in its entirety, + including the disclaimer of warranties. + +2. Redistributions in binary form must reproduce all prior and current + copyright notices, this list of conditions, and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +3. The name of any author may not be used to endorse or promote + products derived from this software without their specific prior + written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +------------------------------------------------------------------------- +GPL v2.0: +--------- + +ALTERNATIVELY, this product may be distributed under the terms of the +GNU General Public License (v2.0 - see below), in which case the +provisions of the GNU GPL are required INSTEAD OF the above +restrictions. (This clause is necessary due to a potential conflict +between the GNU GPL and the restrictions contained in a BSD-style +copyright.) + +------------------------- +Full text of gpl-2.0.txt: +------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/kernel.org/pub/linux/libs/security/libcap/psx/README b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/README new file mode 100644 index 00000000000..e4f90014275 --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/README @@ -0,0 +1,28 @@ +Package "psx" provides an API for invoking system calls in a way that +each system call is mirrored on all OS threads of the combined Go/CGo +runtime. Since the Go runtime treats OS threads as interchangeable, a +feature like this is needed to meaningfully change process privilege +(including dropping privilege) in a Go program running on Linux. This +package is required by: + + "kernel.org/pub/linux/libs/security/libcap/cap" + +When compiled CGO_ENABLED=0, the functionality requires go1.16+ to +build. That release of Go introduced syscall.AllThreadsSyscall*() +APIs. When compiled this way, the "psx" package functions +psx.Syscall3() and psx.Syscall6() are aliased to +syscall.AllThreadsSyscall() and syscall.AllThreadsSyscall6() +respectively. + +When compiled CGO_ENABLED=1, the functionality is implemented by C +code, [lib]psx, which is distributed with libcap. + +The official release announcement site for libcap and libpsx is: + + https://sites.google.com/site/fullycapable/ + +Like libcap/libpsx itself, the "psx" package is distributed with a +"you choose" License. Specifically: BSD three clause, or GPL2. See the +License file. + +Andrew G. Morgan diff --git a/vendor/kernel.org/pub/linux/libs/security/libcap/psx/doc.go b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/doc.go new file mode 100644 index 00000000000..384b0d3e5e2 --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/doc.go @@ -0,0 +1,60 @@ +// Package psx provides support for system calls that are run +// simultanously on all threads under Linux. +// +// This property can be used to work around a historical lack of +// native Go support for such a feature. Something that is the subject +// of: +// +// https://github.com/golang/go/issues/1435 +// +// The package works differently depending on whether or not +// CGO_ENABLED is 0 or 1. +// +// In the former case, psx is a low overhead wrapper for the two +// native go calls: syscall.AllThreadsSyscall() and +// syscall.AllThreadsSyscall6() introduced in go1.16. We provide this +// wrapping to minimize client source code changes when compiling with +// or without CGo enabled. +// +// In the latter case, and toolchains prior to go1.16, it works via +// CGo wrappers for system call functions that call the C [lib]psx +// functions of these names. This ensures that the system calls +// execute simultaneously on all the pthreads of the Go (and CGo) +// combined runtime. +// +// With CGo, the psx support works in the following way: the pthread +// that is first asked to execute the syscall does so, and determines +// if it succeeds or fails. If it fails, it returns immediately +// without attempting the syscall on other pthreads. If the initial +// attempt succeeds, however, then the runtime is stopped in order for +// the same system call to be performed on all the remaining pthreads +// of the runtime. Once all pthreads have completed the syscall, the +// return codes are those obtained by the first pthread's invocation +// of the syscall. +// +// Note, there is no need to use this variant of syscall where the +// syscalls only read state from the kernel. However, since Go's +// runtime freely migrates code execution between pthreads, support of +// this type is required for any successful attempt to fully drop or +// modify the privilege of a running Go program under Linux. +// +// More info on how Linux privilege works and examples of using this +// package can be found here: +// +// https://sites.google.com/site/fullycapable +// +// WARNING: For older go toolchains (prior to go1.15), correct +// compilation of this package may require an extra workaround step: +// +// The workaround is to build with the following CGO_LDFLAGS_ALLOW in +// effect (here the syntax is that of bash for defining an environment +// variable): +// +// export CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*" +// +// +// Copyright (c) 2019,20 Andrew G. Morgan +// +// The psx package is licensed with a (you choose) BSD 3-clause or +// GPL2. See LICENSE file for details. +package psx // import "kernel.org/pub/linux/libs/security/libcap/psx" diff --git a/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx.c b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx.c new file mode 100644 index 00000000000..12dbbc56bee --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx.c @@ -0,0 +1,644 @@ +/* + * Copyright (c) 2019-21 Andrew G Morgan + * + * This file contains a collection of routines that perform thread + * synchronization to ensure that a whole process is running as a + * single privilege entity - independent of the number of pthreads. + * + * The whole file would be unnecessary if glibc exported an explicit + * psx_syscall()-like function that leveraged the nptl:setxid + * mechanism to synchronize thread state over the whole process. + */ +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 199309L + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "psx_syscall.h" + +/* + * psx_load_syscalls() can be weakly defined in dependent libraries to + * provide a mechanism for a library to optionally leverage this psx + * mechanism. Specifically, when libcap calls psx_load_sycalls() it + * provides a weakly declared default that maps its system calls to + * the regular system call functions. However, when linked with psx, + * this function here overrides the syscalls to be the psx ones. + */ +void psx_load_syscalls(long int (**syscall_fn)(long int, + long int, long int, long int), + long int (**syscall6_fn)(long int, + long int, long int, long int, + long int, long int, long int)) +{ + *syscall_fn = psx_syscall3; + *syscall6_fn = psx_syscall6; +} + +/* + * type to keep track of registered threads. + */ +typedef struct registered_thread_s { + struct registered_thread_s *next, *prev; + pthread_t thread; + pthread_mutex_t mu; + int pending; + int gone; +} registered_thread_t; + +static pthread_once_t psx_tracker_initialized = PTHREAD_ONCE_INIT; + +typedef enum { + _PSX_IDLE = 0, + _PSX_SETUP = 1, + _PSX_SYSCALL = 2, + _PSX_CREATE = 3, + _PSX_INFORK = 4, + _PSX_EXITING = 5, +} psx_tracker_state_t; + +/* + * This global structure holds the global coordination state for + * libcap's psx_posix_syscall() support. + */ +static struct psx_tracker_s { + int has_forked; + + pthread_mutex_t state_mu; + pthread_cond_t cond; /* this is only used to wait on 'state' changes */ + psx_tracker_state_t state; + int initialized; + int psx_sig; + + struct { + long syscall_nr; + long arg1, arg2, arg3, arg4, arg5, arg6; + int six; + int active; + } cmd; + + struct sigaction sig_action; + struct sigaction chained_action; + registered_thread_t *root; +} psx_tracker; + +/* + * psx_action_key is used for thread local storage of the thread's + * registration. + */ +pthread_key_t psx_action_key; + +/* + * psx_do_registration called locked and creates a tracker entry for + * the current thread with a TLS specific key pointing at the threads + * specific tracker. + */ +static void *psx_do_registration(void) { + registered_thread_t *node = calloc(1, sizeof(registered_thread_t)); + pthread_mutex_init(&node->mu, NULL); + node->thread = pthread_self(); + pthread_setspecific(psx_action_key, node); + node->next = psx_tracker.root; + if (node->next) { + node->next->prev = node; + } + psx_tracker.root = node; + return node; +} + +/* + * psx_posix_syscall_actor performs the system call on the targeted + * thread and signals it is no longer pending. + */ +static void psx_posix_syscall_actor(int signum, siginfo_t *info, void *ignore) { + /* bail early if this isn't something we recognize */ + if (signum != psx_tracker.psx_sig || !psx_tracker.cmd.active || + info == NULL || info->si_code != SI_TKILL || info->si_pid != getpid()) { + if (psx_tracker.chained_action.sa_sigaction != 0) { + psx_tracker.chained_action.sa_sigaction(signum, info, ignore); + } + return; + } + + if (!psx_tracker.cmd.six) { + (void) syscall(psx_tracker.cmd.syscall_nr, + psx_tracker.cmd.arg1, + psx_tracker.cmd.arg2, + psx_tracker.cmd.arg3); + } else { + (void) syscall(psx_tracker.cmd.syscall_nr, + psx_tracker.cmd.arg1, + psx_tracker.cmd.arg2, + psx_tracker.cmd.arg3, + psx_tracker.cmd.arg4, + psx_tracker.cmd.arg5, + psx_tracker.cmd.arg6); + } + + /* + * This handler can only be called on registered threads which + * have had this specific defined at start-up. (But see the + * subsequent test.) + */ + registered_thread_t *ref = pthread_getspecific(psx_action_key); + if (ref) { + pthread_mutex_lock(&ref->mu); + ref->pending = 0; + pthread_mutex_unlock(&ref->mu); + } /* + * else thread must be dying and its psx_action_key has already + * been cleaned up. + */ +} + +/* + * Some forward declarations for the initialization + * psx_syscall_start() routine. + */ +static void _psx_prepare_fork(void); +static void _psx_fork_completed(void); +static void _psx_forked_child(void); +int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg); + +/* + * psx requires this function to be provided by the linkage wrapping. + */ +extern int __real_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg); + +/* + * psx_confirm_sigaction reconfirms that the psx handler is the first + * handler to respond to the psx signal. It assumes that + * psx_tracker.psx_sig has been set. + */ +static void psx_confirm_sigaction(void) { + sigset_t mask, orig; + struct sigaction existing_sa; + + /* + * Block interrupts while potentially rewriting the handler. + */ + sigemptyset(&mask); + sigaddset(&mask, psx_tracker.psx_sig); + sigprocmask(SIG_BLOCK, &mask, &orig); + + sigaction(psx_tracker.psx_sig, NULL, &existing_sa); + if (existing_sa.sa_sigaction != psx_posix_syscall_actor) { + memcpy(&psx_tracker.chained_action, &existing_sa, sizeof(struct sigaction)); + psx_tracker.sig_action.sa_sigaction = psx_posix_syscall_actor; + sigemptyset(&psx_tracker.sig_action.sa_mask); + psx_tracker.sig_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; + sigaction(psx_tracker.psx_sig, &psx_tracker.sig_action, NULL); + } + + sigprocmask(SIG_SETMASK, &orig, NULL); +} + +/* + * psx_syscall_start initializes the subsystem including initializing + * the mutex. + */ +static void psx_syscall_start(void) { + pthread_mutex_init(&psx_tracker.state_mu, NULL); + pthread_cond_init(&psx_tracker.cond, NULL); + pthread_key_create(&psx_action_key, NULL); + pthread_atfork(_psx_prepare_fork, _psx_fork_completed, _psx_forked_child); + + /* + * All sorts of things are assumed by Linux and glibc and/or musl + * about signal handlers and which can be blocked. Go has its own + * idiosyncrasies too. We tried SIGRTMAX until + * + * https://bugzilla.kernel.org/show_bug.cgi?id=210533 + * + * Our current strategy is to aggressively intercept SIGSYS. + */ + psx_tracker.psx_sig = SIGSYS; + + psx_confirm_sigaction(); + psx_do_registration(); // register the main thread. + + psx_tracker.initialized = 1; +} + +/* + * This is the only way this library globally locks. Note, this is not + * to be confused with psx_sig (interrupt) blocking - which is + * performed around thread creation and when the signal handler is + * being confirmed. + */ +static void psx_lock(void) +{ + pthread_once(&psx_tracker_initialized, psx_syscall_start); + pthread_mutex_lock(&psx_tracker.state_mu); +} + +/* + * This is the only way this library unlocks. + */ +static void psx_unlock(void) +{ + pthread_mutex_unlock(&psx_tracker.state_mu); +} + +/* + * under lock perform a state transition. + */ +static void psx_new_state(psx_tracker_state_t was, psx_tracker_state_t is) +{ + psx_lock(); + while (psx_tracker.state != was) { + pthread_cond_wait(&psx_tracker.cond, &psx_tracker.state_mu); + } + psx_tracker.state = is; + if (is == _PSX_IDLE) { + /* only announce newly idle states since that is all we wait for */ + pthread_cond_signal(&psx_tracker.cond); + } + psx_unlock(); +} + +long int psx_syscall3(long int syscall_nr, + long int arg1, long int arg2, long int arg3) { + return psx_syscall(syscall_nr, arg1, arg2, arg3); +} + +long int psx_syscall6(long int syscall_nr, + long int arg1, long int arg2, long int arg3, + long int arg4, long int arg5, long int arg6) { + return psx_syscall(syscall_nr, arg1, arg2, arg3, arg4, arg5, arg6); +} + +static void _psx_prepare_fork(void) { + /* + * obtain global lock - we don't want any syscalls while the fork + * is occurring since it may interfere with the preparation for + * the fork. + */ + psx_new_state(_PSX_IDLE, _PSX_INFORK); +} + +static void _psx_fork_completed(void) { + /* + * The only way we can get here is if state is _PSX_INFORK and was + * previously _PSX_IDLE. Now that the fork has completed, the + * parent can continue as if it hadn't happened - the forked child + * does not tie its security state to that of the parent process + * and threads. + * + * We don't strictly need to change the psx_tracker.state since we + * hold the mutex over the fork, but we do to make deadlock + * debugging easier. + */ + psx_new_state(_PSX_INFORK, _PSX_IDLE); +} + +static void _psx_forked_child(void) { + /* + * The only way we can get here is if state is _PSX_INFORK and was + * previously _PSX_IDLE. However, none of the registered threads + * exist in this newly minted child process, so we have to reset + * the tracking structure to avoid any confusion. We also scuttle + * any chance of the PSX API working on more than one thread in + * the child by leaving the state as _PSX_INFORK. We do support + * all psx_syscall()s by reverting to them being direct in the + * fork()ed child. + * + * We do this because the glibc man page for fork() suggests that + * only a subset of things will work post fork(). Specifically, + * only a "async-signal-safe functions (see signal- safety(7)) + * until such time as it calls execve(2)" can be relied upon. That + * man page suggests that you can't expect mutexes to work: "not + * async-signal-safe because it uses pthread_mutex_lock(3) + * internally.". + */ + registered_thread_t *next, *old_root; + old_root = psx_tracker.root; + psx_tracker.root = NULL; + + psx_tracker.has_forked = 1; + + for (; old_root; old_root = next) { + next = old_root->next; + memset(old_root, 0, sizeof(*old_root)); + free(old_root); + } +} + +/* + * called locked to unregister a node from the tracker. + */ +static void psx_do_unregister(registered_thread_t *node) { + if (psx_tracker.root == node) { + psx_tracker.root = node->next; + } + if (node->next) { + node->next->prev = node->prev; + } + if (node->prev) { + node->prev->next = node->next; + } + pthread_mutex_destroy(&node->mu); + memset(node, 0, sizeof(*node)); + free(node); +} + +typedef struct { + void *(*fn)(void *); + void *arg; + sigset_t sigbits; +} psx_starter_t; + +/* + * _psx_exiting is used to cleanup the node for the thread on its exit + * path. This is needed for musl libc: + * + * https://bugzilla.kernel.org/show_bug.cgi?id=208477 + * + * and likely wise for glibc too: + * + * https://sourceware.org/bugzilla/show_bug.cgi?id=12889 + */ +static void _psx_exiting(void *node) { + /* + * Until we are in the _PSX_EXITING state, we must not block the + * psx_sig interrupt for this dying thread. That is, until this + * exiting thread can set ref->gone to 1, this dying thread is + * still participating in the psx syscall distribution. + * + * See https://github.com/golang/go/issues/42494 for a situation + * where this code is called with psx_tracker.psx_sig blocked. + */ + sigset_t sigbit, orig_sigbits; + sigemptyset(&sigbit); + pthread_sigmask(SIG_UNBLOCK, &sigbit, &orig_sigbits); + sigaddset(&sigbit, psx_tracker.psx_sig); + pthread_sigmask(SIG_UNBLOCK, &sigbit, NULL); + + /* + * With psx_tracker.psx_sig unblocked we can wait until this + * thread can enter the _PSX_EXITING state. + */ + psx_new_state(_PSX_IDLE, _PSX_EXITING); + + /* + * We now indicate that this thread is no longer participating in + * the psx mechanism. + */ + registered_thread_t *ref = node; + pthread_mutex_lock(&ref->mu); + ref->gone = 1; + pthread_mutex_unlock(&ref->mu); + + /* + * At this point, we can restore the calling sigmask to whatever + * the caller thought was appropriate for a dying thread to have. + */ + pthread_sigmask(SIG_SETMASK, &orig_sigbits, NULL); + + /* + * Allow the rest of the psx system carry on as per normal. + */ + psx_new_state(_PSX_EXITING, _PSX_IDLE); +} + +/* + * _psx_start_fn is a trampoline for the intended start function, it + * is called blocked (_PSX_CREATE), but releases the block before + * calling starter->fn. Before releasing the block, the TLS specific + * attributes are initialized for use by the interrupt handler under + * the psx mutex, so it doesn't race with an interrupt received by + * this thread and the interrupt handler does not need to poll for + * that specific attribute to be present (which is problematic during + * thread shutdown). + */ +static void *_psx_start_fn(void *data) { + void *node = psx_do_registration(); + + psx_new_state(_PSX_CREATE, _PSX_IDLE); + + psx_starter_t *starter = data; + pthread_sigmask(SIG_SETMASK, &starter->sigbits, NULL); + void *(*fn)(void *) = starter->fn; + void *arg = starter->arg; + + memset(data, 0, sizeof(*starter)); + free(data); + + void *ret; + + pthread_cleanup_push(_psx_exiting, node); + ret = fn(arg); + pthread_cleanup_pop(1); + + return ret; +} + +/* + * __wrap_pthread_create is the wrapped destination of all regular + * pthread_create calls. + */ +int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg) { + psx_starter_t *starter = calloc(1, sizeof(psx_starter_t)); + if (starter == NULL) { + perror("failed at thread creation"); + exit(1); + } + starter->fn = start_routine; + starter->arg = arg; + /* + * Until we are in the _PSX_IDLE state and locked, we must not + * block the psx_sig interrupt for this parent thread. Arrange + * that parent thread and newly created one can restore signal + * mask. + */ + sigset_t sigbit, orig_sigbits; + sigemptyset(&sigbit); + pthread_sigmask(SIG_UNBLOCK, &sigbit, &starter->sigbits); + sigaddset(&sigbit, psx_tracker.psx_sig); + pthread_sigmask(SIG_UNBLOCK, &sigbit, &orig_sigbits); + + psx_new_state(_PSX_IDLE, _PSX_CREATE); + + /* + * until the child thread has been blessed with its own TLS + * specific attribute(s) we prevent either the parent thread or + * the new one from experiencing a PSX interrupt. + */ + pthread_sigmask(SIG_BLOCK, &sigbit, NULL); + + int ret = __real_pthread_create(thread, attr, _psx_start_fn, starter); + if (ret == -1) { + psx_new_state(_PSX_CREATE, _PSX_IDLE); + memset(starter, 0, sizeof(*starter)); + free(starter); + } /* else unlock happens in _psx_start_fn */ + + /* the parent can once again receive psx interrupt signals */ + pthread_sigmask(SIG_SETMASK, &orig_sigbits, NULL); + + return ret; +} + +/* + * __psx_immediate_syscall does one syscall using the current + * process. + */ +static long int __psx_immediate_syscall(long int syscall_nr, + int count, long int *arg) { + psx_tracker.cmd.syscall_nr = syscall_nr; + psx_tracker.cmd.arg1 = count > 0 ? arg[0] : 0; + psx_tracker.cmd.arg2 = count > 1 ? arg[1] : 0; + psx_tracker.cmd.arg3 = count > 2 ? arg[2] : 0; + + if (count > 3) { + psx_tracker.cmd.six = 1; + psx_tracker.cmd.arg4 = arg[3]; + psx_tracker.cmd.arg5 = count > 4 ? arg[4] : 0; + psx_tracker.cmd.arg6 = count > 5 ? arg[5] : 0; + return syscall(syscall_nr, + psx_tracker.cmd.arg1, + psx_tracker.cmd.arg2, + psx_tracker.cmd.arg3, + psx_tracker.cmd.arg4, + psx_tracker.cmd.arg5, + psx_tracker.cmd.arg6); + } + + psx_tracker.cmd.six = 0; + return syscall(syscall_nr, psx_tracker.cmd.arg1, + psx_tracker.cmd.arg2, psx_tracker.cmd.arg3); +} + +/* + * __psx_syscall performs the syscall on the current thread and if no + * error is detected it ensures that the syscall is also performed on + * all (other) registered threads. The return code is the value for + * the first invocation. It uses a trick to figure out how many + * arguments the user has supplied. The other half of the trick is + * provided by the macro psx_syscall() in the + * file. The trick is the 7th optional argument (8th over all) to + * __psx_syscall is the count of arguments supplied to psx_syscall. + * + * User: + * psx_syscall(nr, a, b); + * Expanded by macro to: + * __psx_syscall(nr, a, b, 6, 5, 4, 3, 2, 1, 0); + * The eighth arg is now ------------------------------------^ + */ +long int __psx_syscall(long int syscall_nr, ...) { + long int arg[7]; + int i; + + va_list aptr; + va_start(aptr, syscall_nr); + for (i = 0; i < 7; i++) { + arg[i] = va_arg(aptr, long int); + } + va_end(aptr); + + int count = arg[6]; + if (count < 0 || count > 6) { + errno = EINVAL; + return -1; + } + + if (psx_tracker.has_forked) { + return __psx_immediate_syscall(syscall_nr, count, arg); + } + + psx_new_state(_PSX_IDLE, _PSX_SETUP); + psx_confirm_sigaction(); + + long int ret; + + ret = __psx_immediate_syscall(syscall_nr, count, arg); + if (ret == -1 || !psx_tracker.initialized) { + psx_new_state(_PSX_SETUP, _PSX_IDLE); + goto defer; + } + + int restore_errno = errno; + + psx_new_state(_PSX_SETUP, _PSX_SYSCALL); + psx_tracker.cmd.active = 1; + + pthread_t self = pthread_self(); + registered_thread_t *next = NULL, *ref; + + psx_lock(); + for (ref = psx_tracker.root; ref; ref = next) { + next = ref->next; + if (ref->thread == self) { + continue; + } + pthread_mutex_lock(&ref->mu); + ref->pending = 1; + int gone = ref->gone; + if (!gone) { + gone = pthread_kill(ref->thread, psx_tracker.psx_sig) != 0; + } + pthread_mutex_unlock(&ref->mu); + if (!gone) { + continue; + } + /* + * need to remove invalid thread id from linked list + */ + psx_do_unregister(ref); + } + psx_unlock(); + + for (;;) { + int waiting = 0; + psx_lock(); + for (ref = psx_tracker.root; ref; ref = next) { + next = ref->next; + if (ref->thread == self) { + continue; + } + + pthread_mutex_lock(&ref->mu); + int pending = ref->pending; + int gone = ref->gone; + if (pending && !gone) { + gone = (pthread_kill(ref->thread, 0) != 0); + } + pthread_mutex_unlock(&ref->mu); + if (!gone) { + waiting += pending; + continue; + } + /* + * need to remove invalid thread id from linked list + */ + psx_do_unregister(ref); + } + psx_unlock(); + if (!waiting) { + break; + } + sched_yield(); + } + + errno = restore_errno; + psx_tracker.cmd.active = 0; + psx_new_state(_PSX_SYSCALL, _PSX_IDLE); + +defer: + return ret; +} diff --git a/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx.go b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx.go new file mode 100644 index 00000000000..77648e2f0ed --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx.go @@ -0,0 +1,19 @@ +// +build linux,!cgo +// +build go1.16 + +package psx // import "kernel.org/pub/linux/libs/security/libcap/psx" + +import "syscall" + +// Documentation for these functions are provided in the psx_cgo.go +// file. + +//go:uintptrescapes +func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) { + return syscall.AllThreadsSyscall(syscallnr, arg1, arg2, arg3) +} + +//go:uintptrescapes +func Syscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) (uintptr, uintptr, syscall.Errno) { + return syscall.AllThreadsSyscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6) +} diff --git a/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx_cgo.go b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx_cgo.go new file mode 100644 index 00000000000..26aa15a474f --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx_cgo.go @@ -0,0 +1,79 @@ +// +build linux,cgo + +package psx // import "kernel.org/pub/linux/libs/security/libcap/psx" + +import ( + "runtime" + "syscall" +) + +// #cgo LDFLAGS: -lpthread -Wl,-wrap,pthread_create +// +// #include +// #include "psx_syscall.h" +// +// long __errno_too(long set_errno) { +// long v = errno; +// if (set_errno >= 0) { +// errno = set_errno; +// } +// return v; +// } +import "C" + +// setErrno returns the current C.errno value and, if v >= 0, sets the +// CGo errno for a random pthread to value v. If you want some +// consistency, this needs to be called from runtime.LockOSThread() +// code. This function is only defined for testing purposes. The psx.c +// code should properly handle the case that a non-zero errno is saved +// and restored independently of what these Syscall[36]() functions +// observe. +func setErrno(v int) int { + return int(C.__errno_too(C.long(v))) +} + +//go:uintptrescapes + +// Syscall3 performs a 3 argument syscall. Syscall3 differs from +// syscall.[Raw]Syscall() insofar as it is simultaneously executed on +// every thread of the combined Go and CGo runtimes. It works +// differently depending on whether CGO_ENABLED is 1 or 0 at compile +// time. +// +// If CGO_ENABLED=1 it uses the libpsx function C.psx_syscall3(). +// +// If CGO_ENABLED=0 it redirects to the go1.16+ +// syscall.AllThreadsSyscall() function. +func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) { + // We lock to the OSThread here because we may need errno to + // be the one for this thread. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + v := C.psx_syscall3(C.long(syscallnr), C.long(arg1), C.long(arg2), C.long(arg3)) + var errno syscall.Errno + if v < 0 { + errno = syscall.Errno(C.__errno_too(-1)) + } + return uintptr(v), uintptr(v), errno +} + +//go:uintptrescapes + +// Syscall6 performs a 6 argument syscall on every thread of the +// combined Go and CGo runtimes. Other than the number of syscall +// arguments, its behavior is identical to that of Syscall3() - see +// above for the full documentation. +func Syscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) (uintptr, uintptr, syscall.Errno) { + // We lock to the OSThread here because we may need errno to + // be the one for this thread. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + v := C.psx_syscall6(C.long(syscallnr), C.long(arg1), C.long(arg2), C.long(arg3), C.long(arg4), C.long(arg5), C.long(arg6)) + var errno syscall.Errno + if v < 0 { + errno = syscall.Errno(C.__errno_too(-1)) + } + return uintptr(v), uintptr(v), errno +} diff --git a/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx_syscall.h b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx_syscall.h new file mode 100644 index 00000000000..4aacfabdfb1 --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx_syscall.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Andrew G. Morgan + * + * This header, and the -lpsx library, provide a number of things to + * support POSIX semantics for syscalls associated with the pthread + * library. Linking this code is tricky and is done as follows: + * + * ld ... -lpsx -lpthread --wrap=pthread_create + * or, gcc ... -lpsx -lpthread -Wl,-wrap,pthread_create + * + * glibc provides a subset of this functionality natively through the + * nptl:setxid mechanism and could implement psx_syscall() directly + * using that style of functionality but, as of 2019-11-30, the setxid + * mechanism is limited to 9 specific set*() syscalls that do not + * support the syscall6 API (needed for prctl functions and the ambient + * capabilities set for example). + */ + +#ifndef _SYS_PSX_SYSCALL_H +#define _SYS_PSX_SYSCALL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * psx_syscall performs the specified syscall on all psx registered + * threads. The mechanism by which this occurs is much less efficient + * than a standard system call on Linux, so it should only be used + * when POSIX semantics are required to change process relevant + * security state. + * + * Glibc has native support for POSIX semantics on setgroups() and the + * 8 set*[gu]id() functions. So, there is no need to use psx_syscall() + * for these calls. This call exists for all the other system calls + * that need to maintain parity on all pthreads of a program. + * + * Some macrology is used to allow the caller to provide only as many + * arguments as needed, thus psx_syscall() cannot be used as a + * function pointer. For those situations, we define psx_syscall3() + * and psx_syscall6(). + */ +#define psx_syscall(syscall_nr, ...) \ + __psx_syscall(syscall_nr, __VA_ARGS__, (long int) 6, (long int) 5, \ + (long int) 4, (long int) 3, (long int) 2, \ + (long int) 1, (long int) 0) +long int __psx_syscall(long int syscall_nr, ...); +long int psx_syscall3(long int syscall_nr, + long int arg1, long int arg2, long int arg3); +long int psx_syscall6(long int syscall_nr, + long int arg1, long int arg2, long int arg3, + long int arg4, long int arg5, long int arg6); + +/* + * This function should be used by systems to obtain pointers to the + * two syscall functions provided by the PSX library. A linkage trick + * is to define this function as weak in a library that can optionally + * use libpsx and then, should the caller link -lpsx, that library can + * implicitly use these POSIX semantics syscalls. See libcap for an + * example of this useage. + */ +void psx_load_syscalls(long int (**syscall_fn)(long int, + long int, long int, long int), + long int (**syscall6_fn)(long int, + long int, long int, long int, + long int, long int, long int)); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_PSX_SYSCALL_H */ diff --git a/vendor/modules.txt b/vendor/modules.txt index a0e87062072..a6b5d192a16 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -36,6 +36,10 @@ github.com/docker/go-units # github.com/godbus/dbus/v5 v5.1.0 ## explicit; go 1.12 github.com/godbus/dbus/v5 +# github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946 +## explicit; go 1.16 +github.com/landlock-lsm/go-landlock/landlock +github.com/landlock-lsm/go-landlock/landlock/syscall # github.com/moby/sys/capability v0.4.0 ## explicit; go 1.21 github.com/moby/sys/capability @@ -116,3 +120,6 @@ google.golang.org/protobuf/reflect/protoreflect google.golang.org/protobuf/reflect/protoregistry google.golang.org/protobuf/runtime/protoiface google.golang.org/protobuf/runtime/protoimpl +# kernel.org/pub/linux/libs/security/libcap/psx v1.2.51 +## explicit; go 1.11 +kernel.org/pub/linux/libs/security/libcap/psx