diff --git a/go.mod b/go.mod index 3c4eb535991..d2d004f752c 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/cyphar/filepath-securejoin v0.4.1 github.com/docker/go-units v0.5.0 github.com/godbus/dbus/v5 v5.1.0 + github.com/landlock-lsm/go-landlock v0.0.0-20250303204525-1544bccde3a3 github.com/moby/sys/capability v0.4.0 github.com/moby/sys/mountinfo v0.7.2 github.com/moby/sys/user v0.4.0 @@ -31,4 +32,5 @@ require ( github.com/cilium/ebpf v0.17.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 // indirect ) diff --git a/go.sum b/go.sum index c9c14135eb3..9176561e79a 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,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-20250303204525-1544bccde3a3 h1:zcMi8R8vP0WrrXlFMNUBpDy/ydo3sTnCcUPowq1XmSc= +github.com/landlock-lsm/go-landlock v0.0.0-20250303204525-1544bccde3a3/go.mod h1:RSub3ourNF8Hf+swvw49Catm3s7HVf4hzdFxDUnEzdA= 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= @@ -93,3 +95,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.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.70/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go index 07216cb81cd..9e248b98f77 100644 --- a/libcontainer/configs/config.go +++ b/libcontainer/configs/config.go @@ -238,6 +238,9 @@ type Config struct { // ExecCPUAffinity is CPU affinity for a non-init process to be run in the container. ExecCPUAffinity *CPUAffinity `json:"exec_cpu_affinity,omitempty"` + + // Landlock contains configuration for Landlock LSM restrictions. + Landlock *LandlockConfig `json:"landlock,omitempty"` } // Scheduler is based on the Linux sched_setattr(2) syscall. diff --git a/libcontainer/configs/landlock.go b/libcontainer/configs/landlock.go new file mode 100644 index 00000000000..aa172cf4c78 --- /dev/null +++ b/libcontainer/configs/landlock.go @@ -0,0 +1,11 @@ +package configs + +type LandlockConfig struct { + Mode string `json:"mode"` // "enforce"|"best-effort" + RoDirs []string `json:"roDirs"` + RwDirs []string `json:"rwDirs"` + WithRefer []string `json:"withRefer"` // dirs that need cross-dir rename/link + IoctlDev []string `json:"ioctlDev"` // device paths requiring ioctl + BindTCP []uint16 `json:"bindTCP"` + ConnectTCP []uint16 `json:"connectTCP"` +} diff --git a/libcontainer/landlock/apply_linux.go b/libcontainer/landlock/apply_linux.go new file mode 100644 index 00000000000..7e4bebb86a2 --- /dev/null +++ b/libcontainer/landlock/apply_linux.go @@ -0,0 +1,56 @@ +package landlock + +import ( + "fmt" + + ll "github.com/landlock-lsm/go-landlock/landlock" + "github.com/opencontainers/runc/libcontainer/configs" +) + +func Apply(cfg *configs.LandlockConfig) error { + if cfg == nil { + return nil + } + + // Choose ABI + fallback policy + var c ll.Config + switch cfg.Mode { + case "best-effort": + c = ll.V5.BestEffort() // V5 covers FS+NET+ioctl-dev; will step down automatically + case "enforce": + c = ll.V5 // or ll.V6 once you use scopes + default: + c = ll.V5.BestEffort() + } + + var rules []ll.Rule + + if len(cfg.RoDirs) > 0 { + rules = append(rules, ll.RODirs(cfg.RoDirs...)) + } + if len(cfg.RwDirs) > 0 { + rules = append(rules, ll.RWDirs(cfg.RwDirs...)) + } + for _, d := range cfg.WithRefer { + rules = append(rules, ll.RWDirs(d).WithRefer()) + } + for _, d := range cfg.IoctlDev { + rules = append(rules, ll.RODirs(d).WithIoctlDev()) + } + + for _, p := range cfg.BindTCP { + rules = append(rules, ll.BindTCP(p)) + } + for _, p := range cfg.ConnectTCP { + rules = append(rules, ll.ConnectTCP(p)) + } + + // This sets PR_SET_NO_NEW_PRIVS as needed and then restricts self. + // The library internally queries ABI and degrades if BestEffort(). + if err := c.Restrict(rules...); err != nil { + if cfg.Mode == "enforce" { + return fmt.Errorf("landlock enforce failed: %w", err) + } + } + return nil +} diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go index 6bf5ba39cc8..582c58a074e 100644 --- a/libcontainer/standard_init_linux.go +++ b/libcontainer/standard_init_linux.go @@ -15,6 +15,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" @@ -238,6 +239,12 @@ func (l *linuxStandardInit) Init() error { } } + if l.config.Config.Landlock != nil { + if err := landlock.Apply(l.config.Config.Landlock); err != nil { + return fmt.Errorf("failed to apply landlock restrictions: %w", err) + } + } + // Set personality if specified. if l.config.Config.Personality != nil { if err := setupPersonality(l.config.Config); err != nil { 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..2c1a662b11b --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/abi_versions.go @@ -0,0 +1,61 @@ +package landlock + +import ll "github.com/landlock-lsm/go-landlock/landlock/syscall" + +type abiInfo struct { + version int + supportedAccessFS AccessFSSet + supportedAccessNet AccessNetSet +} + +var abiInfos = []abiInfo{ + { + version: 0, + supportedAccessFS: 0, + }, + { + version: 1, + supportedAccessFS: (1 << 13) - 1, + }, + { + version: 2, + supportedAccessFS: (1 << 14) - 1, + }, + { + version: 3, + supportedAccessFS: (1 << 15) - 1, + }, + { + version: 4, + supportedAccessFS: (1 << 15) - 1, + supportedAccessNet: (1 << 2) - 1, + }, + { + version: 5, + supportedAccessFS: (1 << 16) - 1, + supportedAccessNet: (1 << 2) - 1, + }, +} + +func (a abiInfo) asConfig() Config { + return Config{ + handledAccessFS: a.supportedAccessFS, + handledAccessNet: a.supportedAccessNet, + } +} + +// 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..28ad8c2cecc --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/accessfs.go @@ -0,0 +1,79 @@ +package landlock + +import ( + "fmt" + "strings" +) + +var accessFSNames = []string{ + "execute", + "write_file", + "read_file", + "read_dir", + "remove_dir", + "remove_file", + "make_char", + "make_dir", + "make_reg", + "make_sock", + "make_fifo", + "make_block", + "make_sym", + "refer", + "truncate", + "ioctl_dev", +} + +// AccessFSSet is a set of Landlockable file system access operations. +type AccessFSSet uint64 + +var supportedAccessFS = AccessFSSet((1 << len(accessFSNames)) - 1) + +func accessSetString(a uint64, names []string) string { + if a == 0 { + return "∅" + } + var b strings.Builder + b.WriteByte('{') + for i := 0; i < 64; i++ { + if a&(1< 1 { + b.WriteByte(',') + } + if i < len(names) { + b.WriteString(names[i]) + } else { + b.WriteString(fmt.Sprintf("1<<%v", i)) + } + } + b.WriteByte('}') + return b.String() +} + +func (a AccessFSSet) String() string { + return accessSetString(uint64(a), accessFSNames) +} + +func (a AccessFSSet) isSubset(b AccessFSSet) bool { + return a&b == a +} + +func (a AccessFSSet) intersect(b AccessFSSet) AccessFSSet { + return a & b +} + +func (a AccessFSSet) union(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/accessnet.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/accessnet.go new file mode 100644 index 00000000000..7dc4d9c3990 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/accessnet.go @@ -0,0 +1,31 @@ +package landlock + +// AccessNetSet is a set of Landlockable network access rights. +type AccessNetSet uint64 + +var accessNetNames = []string{ + "bind_tcp", + "connect_tcp", +} + +var supportedAccessNet = AccessNetSet((1 << len(accessNetNames)) - 1) + +func (a AccessNetSet) String() string { + return accessSetString(uint64(a), accessNetNames) +} + +func (a AccessNetSet) isSubset(b AccessNetSet) bool { + return a&b == a +} + +func (a AccessNetSet) intersect(b AccessNetSet) AccessNetSet { + return a & b +} + +func (a AccessNetSet) isEmpty() bool { + return a == 0 +} + +func (a AccessNetSet) valid() bool { + return a.isSubset(supportedAccessNet) +} 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..430920d5194 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/config.go @@ -0,0 +1,298 @@ +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.AccessFSTruncate | 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 | ll.AccessFSTruncate + + // 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 = abiInfos[1].asConfig() + // Landlock V2 support (V1 + file reparenting between different directories) + V2 = abiInfos[2].asConfig() + // Landlock V3 support (V2 + file truncation) + V3 = abiInfos[3].asConfig() + // Landlock V4 support (V3 + networking) + V4 = abiInfos[4].asConfig() + // Landlock V5 support (V4 + ioctl on device files) + V5 = abiInfos[5].asConfig() +) + +// v0 denotes "no Landlock support". Only used internally. +var v0 = Config{} + +// 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 + handledAccessNet AccessNetSet + 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. +// (If you are getting an error, you might need to upgrade to a newer +// version of go-landlock.) +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 { + switch arg := arg.(type) { + case AccessFSSet: + if !c.handledAccessFS.isEmpty() { + return nil, errors.New("only one AccessFSSet may be provided") + } + if !arg.valid() { + return nil, errors.New("unsupported AccessFSSet value; upgrade go-landlock?") + } + c.handledAccessFS = arg + case AccessNetSet: + if !c.handledAccessNet.isEmpty() { + return nil, errors.New("only one AccessNetSet may be provided") + } + if !arg.valid() { + return nil, errors.New("unsupported AccessNetSet value; upgrade go-landlock?") + } + c.handledAccessNet = arg + default: + 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 i := len(abiInfos) - 1; i >= 0; i-- { + a := abiInfos[i] + if c.compatibleWithABI(a) { + abi = a + } + } + + var fsDesc = c.handledAccessFS.String() + if abi.supportedAccessFS == c.handledAccessFS && c.handledAccessFS != 0 { + fsDesc = "all" + } + + var netDesc = c.handledAccessNet.String() + if abi.supportedAccessNet == c.handledAccessNet && c.handledAccessNet != 0 { + fsDesc = "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; FS: %v; Net: %v%v}", version, fsDesc, netDesc, 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 +} + +// 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.V3.RestrictPaths( +// landlock.RODirs("/usr", "/bin"), +// landlock.RWDirs("/tmp"), +// ) +// if err != nil { +// log.Fatalf("landlock.V3.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+) +// - Truncating file contents (V3+) +// +// 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+) +// - Renaming or linking a file between directories (V2+) +// +// 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), +// 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 in the +// [Kernel Documentation about 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". This grants the full set of access rights which are +// available within the configuration. +// - [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] rule lets callers define custom subsets of these +// access rights. AccessFSSets permitted using [PathAccess] must be a +// subset of the [AccessFSSet] that the Config restricts. +// +// [Kernel Documentation about Access Rights]: https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights +func (c Config) RestrictPaths(rules ...Rule) error { + c.handledAccessNet = 0 // clear out everything but file system access + return restrict(c, rules...) +} + +// RestrictNet restricts network access in goroutines. +// +// Using Landlock V4, this function will disallow the use of bind(2) +// and connect(2) for TCP ports, unless those TCP ports are +// specifically permitted using these rules: +// +// - [ConnectTCP] permits connect(2) operations to a given TCP port. +// - [BindTCP] permits bind(2) operations on a given TCP port. +// +// These network access rights are documented in more depth in the +// [Kernel Documentation about Network flags]. +// +// [Kernel Documentation about Network flags]: https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#network-flags +func (c Config) RestrictNet(rules ...Rule) error { + c.handledAccessFS = 0 // clear out everything but network access + return restrict(c, rules...) +} + +// Restrict restricts all types of access which is restrictable with the Config. +// +// Using Landlock V4, this is equivalent to calling both +// [RestrictPaths] and [RestrictNet] with the subset of arguments that +// apply to it. +// +// In future Landlock versions, this function might restrict +// additional kinds of operations outside of file system access and +// networking, provided that the [Config] specifies these. +func (c Config) Restrict(rules ...Rule) error { + return restrict(c, rules...) +} + +// PathOpt is a deprecated alias for [Rule]. +// +// Deprecated: This alias is only kept around for backwards +// compatibility and will disappear with the next major release. +type PathOpt = Rule + +// compatibleWith is true if c is compatible to work at the given Landlock ABI level. +func (c Config) compatibleWithABI(abi abiInfo) bool { + return (c.handledAccessFS.isSubset(abi.supportedAccessFS) && + c.handledAccessNet.isSubset(abi.supportedAccessNet)) +} + +// restrictTo returns a config that is a subset of c and which is compatible with the given ABI. +func (c Config) restrictTo(abi abiInfo) Config { + return Config{ + handledAccessFS: c.handledAccessFS.intersect(abi.supportedAccessFS), + handledAccessNet: c.handledAccessNet.intersect(abi.supportedAccessNet), + bestEffort: true, + } +} 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..0e8a0f20b3d --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/landlock.go @@ -0,0 +1,111 @@ +// Package landlock restricts a Go program's ability to use files and networking. +// +// # Restricting file access +// +// 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.V5.BestEffort().RestrictPaths( +// landlock.RODirs("/usr", "/bin"), +// landlock.RWDirs("/tmp"), +// ) +// +// This will restrict file access using Landlock V5, 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. +// +// # Restricting networking +// +// The following invocation will restrict all goroutines so that they +// can only bind to TCP port 8080 and only connect to TCP port 53: +// +// err := landlock.V5.BestEffort().RestrictNet( +// landlock.BindTCP(8080), +// landlock.ConnectTCP(53), +// ) +// +// This functionality is available since Landlock V5. +// +// # Restricting file access and networking at once +// +// The following invocation restricts both file and network access at +// once. The effect is the same as calling [Config.RestrictPaths] and +// [Config.RestrictNet] one after another, but it happens in one step. +// +// err := landlock.V5.BestEffort().Restrict( +// landlock.RODirs("/usr", "/bin"), +// landlock.RWDirs("/tmp"), +// landlock.BindTCP(8080), +// landlock.ConnectTCP(53), +// ) +// +// # More possible invocations +// +// landlock.V5.RestrictPaths(...) (without the call to +// [Config.BestEffort]) enforces the given rules using the +// capabilities of Landlock V5, but returns an error if that +// functionality is not available on the system that the program is +// running on. +// +// # Landlock ABI versioning +// +// The Landlock ABI is versioned, so that callers can probe for the +// availability of different Landlock features. +// +// When using the Go Landlock package, callers need to identify at +// which ABI level they want to use Landlock and call one of the +// restriction methods (e.g. [Config.RestrictPaths]) on the +// corresponding ABI constant. +// +// When new Landlock versions become available in landlock, users will +// manually need to upgrade their usages 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. +// +// In this case, the Go Landlock library will enforce as much as +// possible, but it will ensure that all the requested access rights +// are permitted after Landlock enforcement. +// +// # Current limitations +// +// Landlock can not currently restrict all file system operations. +// The operations that can and can not be restricted yet are listed in +// the [Kernel Documentation about Access Rights]. +// +// Enabling Landlock implicitly turns off the following file system +// features: +// +// - File reparenting: renaming or linking a file to a different parent directory is denied, +// unless it is explicitly enabled on both directories with the "Refer" access modifier, +// and the new target directory does not grant the file additional rights through its +// Landlock access rules. +// - Filesystem topology modification: arbitrary mounts are always denied. +// +// These are Landlock limitations that will be resolved in future +// versions. See the [Kernel Documentation about Current Limitations] +// for more details. +// +// # Multithreading Limitations +// +// 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. +// +// [Kernel Documentation about Access Rights]: https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights +// [Kernel Documentation about Current Limitations]: https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#current-limitations +package landlock diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/net_opt.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/net_opt.go new file mode 100644 index 00000000000..6aa0bd986de --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/net_opt.go @@ -0,0 +1,65 @@ +package landlock + +import ( + "fmt" + + ll "github.com/landlock-lsm/go-landlock/landlock/syscall" +) + +type NetRule struct { + access AccessNetSet + port uint16 +} + +// ConnectTCP is a [Rule] which grants the right to connect a socket +// to a given TCP port. +// +// In Go, the connect(2) operation is usually run as part of +// net.Dial(). +func ConnectTCP(port uint16) NetRule { + return NetRule{ + access: ll.AccessNetConnectTCP, + port: port, + } +} + +// BindTCP is a [Rule] which grants the right to bind a socket to a +// given TCP port. +// +// In Go, the bind(2) operation is usually run as part of +// net.Listen(). +func BindTCP(port uint16) NetRule { + return NetRule{ + access: ll.AccessNetBindTCP, + port: port, + } +} + +func (n NetRule) String() string { + return fmt.Sprintf("ALLOW %v on TCP port %v", n.access, n.port) +} + +func (n NetRule) compatibleWithConfig(c Config) bool { + return n.access.isSubset(c.handledAccessNet) +} + +func (n NetRule) addToRuleset(rulesetFD int, c Config) error { + if n.access == 0 { + // Adding this to the ruleset would be a no-op + // and result in an error. + return nil + } + flags := 0 + attr := &ll.NetPortAttr{ + AllowedAccess: uint64(n.access), + Port: uint64(n.port), + } + return ll.LandlockAddNetPortRule(rulesetFD, attr, flags) +} + +func (n NetRule) downgrade(c Config) (out Rule, ok bool) { + return NetRule{ + access: n.access.intersect(c.handledAccessNet), + port: n.port, + }, true +} diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/opt.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/opt.go new file mode 100644 index 00000000000..dfff0974dd0 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/opt.go @@ -0,0 +1,30 @@ +package landlock + +// Rule represents one or more Landlock rules which can be added to a +// Landlock ruleset. +type Rule interface { + // compatibleWithConfig is true if the given rule is + // compatible with the configuration c. + compatibleWithConfig(c Config) bool + + // downgrade returns a downgraded rule for "best effort" mode, + // under the assumption that the kernel only supports c. + // + // It establishes that: + // + // - rule.accessFS ⊆ handledAccessFS for FSRules + // - rule.accessNet ⊆ handledAccessNet for NetRules + // + // If the rule is unsupportable under the given Config at + // all, ok is false. This happens when c represents a Landlock + // V1 system but the rule wants to grant the refer right on + // a path. "Refer" operations are always forbidden under + // Landlock V1. + downgrade(c Config) (out Rule, ok bool) + + // addToRuleset applies the rule to the given rulesetFD. + // + // This may return errors such as "file not found" depending + // on the rule type. + addToRuleset(rulesetFD int, c Config) error +} diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/path_opt.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/path_opt.go new file mode 100644 index 00000000000..c35b28e3d52 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/path_opt.go @@ -0,0 +1,183 @@ +package landlock + +import ( + "fmt" + + ll "github.com/landlock-lsm/go-landlock/landlock/syscall" +) + +// FSRule is a Rule which permits access to file system paths. +type FSRule struct { + accessFS AccessFSSet + paths []string + enforceSubset bool // enforce that accessFS is a subset of cfg.handledAccessFS + ignoreMissing bool // ignore missing paths +} + +// withRights adds the given access rights to the rights enforced in the FSRule +// and returns the result as a new FSRule. +func (r FSRule) withRights(a AccessFSSet) FSRule { + r.accessFS = r.accessFS.union(a) + return r +} + +// intersectRights intersects the given access rights with the rights +// enforced in the FSRule and returns the result as a new FSRule. +func (r FSRule) intersectRights(a AccessFSSet) FSRule { + r.accessFS = r.accessFS.intersect(a) + return r +} + +// WithRefer adds the "refer" access right to a FSRule. +// +// Notably, asking for the "refer" access right does not work on +// kernels below 5.19. In best effort mode, this will fall back to not +// using Landlock enforcement at all on these kernel versions. If you +// want to use Landlock on these kernels, do not use the "refer" +// access right. +func (r FSRule) WithRefer() FSRule { + return r.withRights(ll.AccessFSRefer) +} + +// WithIoctlDev adds the "ioctl dev" access right to a FSRule. +// +// It is uncommon to need this access right, so it is not part of +// [RWFiles] or [RWDirs]. +func (r FSRule) WithIoctlDev() FSRule { + return r.withRights(ll.AccessFSIoctlDev) +} + +// IgnoreIfMissing gracefully ignores missing paths. +// +// Under normal circumstances, referring to a non-existing path in a rule would +// lead to a runtime error. When the rule uses the IgnoreIfMissing modifier, +// these runtime errors are ignored. This can be useful e.g. for optional +// configuration paths, which are only ever read by a program. +func (r FSRule) IgnoreIfMissing() FSRule { + r.ignoreMissing = true + return r +} + +func (r FSRule) String() string { + return fmt.Sprintf("REQUIRE %v for paths %v", r.accessFS, r.paths) +} + +// compatibleWithConfig returns true if the given rule is compatible +// for use with the config c. +func (r FSRule) compatibleWithConfig(c Config) bool { + a := r.accessFS + if !r.enforceSubset { + // If !enforceSubset, this FSRule is potentially overspecifying flags, + // so we should not check the subset property. We make an exception + // for the "refer" flag, which should still get checked though. + a = a.intersect(ll.AccessFSRefer) + } + return a.isSubset(c.handledAccessFS) +} + +// downgrade calculates the actual ruleset to be enforced given the +// current config (and assuming that the config is going to work under +// the running kernel). +// +// It establishes that rule.accessFS ⊆ c.handledAccessFS. +// +// If ok is false, downgrade is impossible and we need to fall back to doing nothing. +func (r FSRule) downgrade(c Config) (out Rule, ok bool) { + // In case that "refer" is requested on a path, we + // require Landlock V2+, or we have to downgrade to V0. + // You can't get the refer capability with V1, but linking/ + // renaming files is always implicitly restricted. + if hasRefer(r.accessFS) && !hasRefer(c.handledAccessFS) { + return FSRule{}, false + } + return r.intersectRights(c.handledAccessFS), true +} + +func hasRefer(a AccessFSSet) bool { + return a&ll.AccessFSRefer != 0 +} + +// PathAccess is a [Rule] which grants the access rights 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 rules 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 about Access Rights]. +// +// accessFS must be a subset of the permissions that the Config +// restricts. +// +// [Kernel Documentation about Access Rights]: https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights +func PathAccess(accessFS AccessFSSet, paths ...string) FSRule { + return FSRule{ + accessFS: accessFS, + paths: paths, + enforceSubset: true, + } +} + +// RODirs is a [Rule] which grants common read-only access to files +// and directories and permits executing files. +func RODirs(paths ...string) FSRule { + return FSRule{ + accessFS: accessFSRead, + paths: paths, + enforceSubset: false, + } +} + +// RWDirs is a [Rule] which grants full (read and write) access to +// files and directories under the given paths. +// +// Noteworthy operations which are *not* covered by RWDirs: +// +// - RWDirs does *not* grant the right to *reparent or link* files +// across different directories. If this access right is +// required, use [FSRule.WithRefer]. +// +// - RWDirs does *not* grant the right to *use IOCTL* on device +// files. If this access right is required, use +// [FSRule.WithIoctlDev]. +func RWDirs(paths ...string) FSRule { + return FSRule{ + accessFS: accessFSReadWrite, + paths: paths, + enforceSubset: false, + } +} + +// ROFiles is a [Rule] which grants common read access to individual +// files, but not to directories, for the file hierarchies under the +// given paths. +func ROFiles(paths ...string) FSRule { + return FSRule{ + accessFS: accessFSRead & accessFile, + paths: paths, + enforceSubset: false, + } +} + +// RWFiles is a [Rule] which grants common read and write access to +// files under the given paths, but it does not permit access to +// directories. +// +// Noteworthy operations which are *not* covered by RWFiles: +// +// - RWFiles does *not* grant the right to *use IOCTL* on device +// files. If this access right is required, use +// [FSRule.WithIoctlDev]. +func RWFiles(paths ...string) FSRule { + return FSRule{ + accessFS: accessFSReadWrite & accessFile, + paths: paths, + enforceSubset: false, + } +} diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/path_opt_linux.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/path_opt_linux.go new file mode 100644 index 00000000000..4d3a6b334ad --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/path_opt_linux.go @@ -0,0 +1,61 @@ +//go:build linux + +package landlock + +import ( + "errors" + "fmt" + "syscall" + + ll "github.com/landlock-lsm/go-landlock/landlock/syscall" + "golang.org/x/sys/unix" +) + +func (r FSRule) addToRuleset(rulesetFD int, c Config) error { + effectiveAccessFS := r.accessFS + if !r.enforceSubset { + effectiveAccessFS = effectiveAccessFS.intersect(c.handledAccessFS) + } + if effectiveAccessFS == 0 { + // Adding this to the ruleset would be a no-op + // and result in an error. + return nil + } + for _, path := range r.paths { + if err := addPath(rulesetFD, path, effectiveAccessFS); err != nil { + if r.ignoreMissing && errors.Is(err, unix.ENOENT) { + continue // Skip this path. + } + return fmt.Errorf("populating ruleset for %q with access %v: %w", path, effectiveAccessFS, err) + } + } + return nil +} + +func addPath(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 addPath() ensures that. + err = fmt.Errorf("inconsistent access rights (using directory access rights on a regular file?): %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 +} diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/path_opt_nonlinux.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/path_opt_nonlinux.go new file mode 100644 index 00000000000..61a28a7a0ec --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/path_opt_nonlinux.go @@ -0,0 +1,9 @@ +//go:build !linux + +package landlock + +import "errors" + +func (r FSRule) addToRuleset(rulesetFD int, c Config) error { + return errors.New("Landlock is only supported on Linux") +} 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..04e5789d4b1 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/restrict.go @@ -0,0 +1,100 @@ +//go:build linux + +package landlock + +import ( + "errors" + "fmt" + "syscall" + + ll "github.com/landlock-lsm/go-landlock/landlock/syscall" + "golang.org/x/sys/unix" +) + +// downgrade calculates the actual ruleset to be enforced given the +// current kernel's Landlock ABI level. +// +// It establishes that rule.compatibleWithConfig(c) and c.compatibleWithABI(abi). +func downgrade(c Config, rules []Rule, abi abiInfo) (Config, []Rule) { + c = c.restrictTo(abi) + + resRules := make([]Rule, 0, len(rules)) + for _, rule := range rules { + rule, ok := rule.downgrade(c) + if !ok { + return v0, nil // Use "ABI V0" (do nothing) + } + resRules = append(resRules, rule) + } + return c, resRules +} + +// restrict is the actual implementation which sets up Landlock. +func restrict(c Config, rules ...Rule) error { + // Check validity of rules early. + for _, rule := range rules { + if !rule.compatibleWithConfig(c) { + return fmt.Errorf("incompatible rule %v: %w", rule, unix.EINVAL) + } + } + + abi := getSupportedABIVersion() + if c.bestEffort { + c, rules = downgrade(c, rules, abi) + } + if !c.compatibleWithABI(abi) { + return fmt.Errorf("missing kernel Landlock support. Got Landlock ABI v%v, wanted %v", abi.version, c) + } + + // TODO: This might be incorrect - the "refer" permission is + // always implicit, even in Landlock V1. So enabling Landlock + // on a Landlock V1 kernel without any handled access rights + // will still forbid linking files between directories. + if c.handledAccessFS.isEmpty() && c.handledAccessNet.isEmpty() { + return nil // Success: Nothing to restrict. + } + + rulesetAttr := ll.RulesetAttr{ + HandledAccessFS: uint64(c.handledAccessFS), + HandledAccessNet: uint64(c.handledAccessNet), + } + 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 _, rule := range rules { + if err := rule.addToRuleset(fd, c); 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 +} + +// 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/restrict_nonlinux.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/restrict_nonlinux.go new file mode 100644 index 00000000000..3731d473145 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/restrict_nonlinux.go @@ -0,0 +1,12 @@ +//go:build !linux + +package landlock + +import "fmt" + +func restrict(c Config, rules ...Rule) error { + if c.bestEffort { + return nil // Fallback to "nothing" + } + return fmt.Errorf("missing kernel Landlock support. Landlock is only supported on Linux") +} 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..ca2fc178d01 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/syscall/landlock.go @@ -0,0 +1,79 @@ +// 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 + +// Landlock file system access rights. +// +// Please see the full documentation at +// https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#filesystem-flags. +const ( + AccessFSExecute = 1 << iota + AccessFSWriteFile + AccessFSReadFile + AccessFSReadDir + AccessFSRemoveDir + AccessFSRemoveFile + AccessFSMakeChar + AccessFSMakeDir + AccessFSMakeReg + AccessFSMakeSock + AccessFSMakeFifo + AccessFSMakeBlock + AccessFSMakeSym + AccessFSRefer + AccessFSTruncate + AccessFSIoctlDev +) + +// Landlock network access rights. +// +// Please see the full documentation at +// https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#network-flags. +const ( + AccessNetBindTCP = 1 << iota + AccessNetConnectTCP +) + +// 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 + HandledAccessNet uint64 +} + +// The size of the RulesetAttr struct in bytes. +const rulesetAttrSize = 16 + +// 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, opened with `O_PATH`, which identifies + // the parent directory of a file hierarchy, or just a file. + ParentFd int +} + +// NetPortAttr specifies which ports can be used for what. +type NetPortAttr struct { + AllowedAccess uint64 + Port uint64 +} diff --git a/vendor/github.com/landlock-lsm/go-landlock/landlock/syscall/syscall_linux.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/syscall/syscall_linux.go new file mode 100644 index 00000000000..5cb271d5678 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/syscall/syscall_linux.go @@ -0,0 +1,79 @@ +//go:build linux + +package syscall + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/unix" + "kernel.org/pub/linux/libs/security/libcap/psx" +) + +// 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) { + r0, _, e1 := syscall.Syscall(unix.SYS_LANDLOCK_CREATE_RULESET, 0, 0, unix.LANDLOCK_CREATE_RULESET_VERSION) + version = int(r0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +// Landlock rule types. +const ( + RuleTypePathBeneath = unix.LANDLOCK_RULE_PATH_BENEATH + RuleTypeNetPort = 2 // TODO: Use it from sys/unix when available. +) + +// 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) +} + +// LandlockAddNetPortRule adds a rule of type "net port" to the given ruleset FD. +// attr defines the rule parameters. flags must currently be 0. +func LandlockAddNetPortRule(rulesetFD int, attr *NetPortAttr, flags int) error { + return LandlockAddRule(rulesetFD, RuleTypeNetPort, 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, arg3, arg4, 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/github.com/landlock-lsm/go-landlock/landlock/syscall/syscall_nonlinux.go b/vendor/github.com/landlock-lsm/go-landlock/landlock/syscall/syscall_nonlinux.go new file mode 100644 index 00000000000..dedf904aaf5 --- /dev/null +++ b/vendor/github.com/landlock-lsm/go-landlock/landlock/syscall/syscall_nonlinux.go @@ -0,0 +1,36 @@ +//go:build !linux + +package syscall + +import ( + "syscall" + "unsafe" +) + +func LandlockCreateRuleset(attr *RulesetAttr, flags int) (fd int, err error) { + return -1, syscall.ENOSYS +} + +func LandlockGetABIVersion() (version int, err error) { + return -1, syscall.ENOSYS +} + +func LandlockAddRule(rulesetFd int, ruleType int, ruleAttr unsafe.Pointer, flags int) (err error) { + return syscall.ENOSYS +} + +func LandlockAddPathBeneathRule(rulesetFd int, attr *PathBeneathAttr, flags int) error { + return syscall.ENOSYS +} + +func LandlockAddNetPortRule(rulesetFD int, attr *NetPortAttr, flags int) error { + return syscall.ENOSYS +} + +func AllThreadsLandlockRestrictSelf(rulesetFd int, flags int) (err error) { + return syscall.ENOSYS +} + +func AllThreadsPrctl(option int, arg2, arg3, arg4, arg5 uintptr) (err error) { + return syscall.ENOSYS +} 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..39108c26a15 --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/License @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-only */ + +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..c4ba829a118 --- /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 +// simultaneously 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..65eb2aaa4cd --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx.c @@ -0,0 +1,765 @@ +/* + * 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" + +#ifdef _PSX_DEBUG_MEMORY + +static void *_psx_calloc(const char *file, const int line, + size_t nmemb, size_t size) { + void *ptr = calloc(nmemb, size); + fprintf(stderr, "psx:%d:%s:%d: calloc(%ld, %ld) -> %p\n", gettid(), + file, line, (long int)nmemb, (long int)size, ptr); + return ptr; +} + +static void _psx_free(const char *file, const int line, void *ptr) { + fprintf(stderr, "psx:%d:%s:%d: free(%p)\n", gettid(), file, line, ptr); + return free(ptr); +} + +#define calloc(a, b) _psx_calloc(__FILE__, __LINE__, a, b) +#define free(a) _psx_free(__FILE__, __LINE__, a) + +#endif /* def _PSX_DEBUG_MEMORY */ + +/* + * 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; + long int retval; + pid_t tid; +} 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; + psx_sensitivity_t sensitivity; + + 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)); + if (node == NULL) { + perror("unable to register psx handler"); + _exit(1); + } + 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; + } + + long int retval; + if (!psx_tracker.cmd.six) { + retval = syscall(psx_tracker.cmd.syscall_nr, + psx_tracker.cmd.arg1, + psx_tracker.cmd.arg2, + psx_tracker.cmd.arg3); + } else { + retval = 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; + ref->retval = retval; + ref->tid = syscall(SYS_gettid); + 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_cleanup(void); +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. */ + atexit(_psx_cleanup); + + 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. Changing state is generally + * done via this function. However, there is a single exception in + * _psx_cleanup(). + */ +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 to 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 > 0) { + 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(); + + int mismatch = 0; + 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 (!gone) { + if (pending) { + gone = (pthread_kill(ref->thread, 0) != 0); + } else { + mismatch |= (ref->retval != ret); + } + } + 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(); + } + + psx_tracker.cmd.active = 0; + if (mismatch) { + psx_lock(); + switch (psx_tracker.sensitivity) { + case PSX_IGNORE: + break; + default: + fprintf(stderr, "psx_syscall result differs.\n"); + if (psx_tracker.cmd.six) { + fprintf(stderr, "trap:%ld a123456=[%ld,%ld,%ld,%ld,%ld,%ld]\n", + 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); + } else { + fprintf(stderr, "trap:%ld a123=[%ld,%ld,%ld]\n", + psx_tracker.cmd.syscall_nr, + psx_tracker.cmd.arg1, + psx_tracker.cmd.arg2, + psx_tracker.cmd.arg3); + } + fprintf(stderr, "results:"); + for (ref = psx_tracker.root; ref; ref = next) { + next = ref->next; + if (ref->thread == self) { + continue; + } + if (ret != ref->retval) { + fprintf(stderr, " %d={%ld}", ref->tid, ref->retval); + } + } + fprintf(stderr, " wanted={%ld}\n", ret); + if (psx_tracker.sensitivity == PSX_WARNING) { + break; + } + pthread_kill(self, SIGSYS); + } + psx_unlock(); + } + errno = restore_errno; + psx_new_state(_PSX_SYSCALL, _PSX_IDLE); + +defer: + return ret; +} + +/* + * _psx_cleanup its called when the program exits. It is used to free + * any memory used by the thread tracker. + */ +static void _psx_cleanup(void) { + registered_thread_t *ref, *next; + + /* + * We enter the exiting state. Unlike exiting a single thread we + * never leave this state since this cleanup is only done at + * program exit. + */ + psx_lock(); + while (psx_tracker.state != _PSX_IDLE && psx_tracker.state != _PSX_INFORK) { + pthread_cond_wait(&psx_tracker.cond, &psx_tracker.state_mu); + } + psx_tracker.state = _PSX_EXITING; + psx_unlock(); + + for (ref = psx_tracker.root; ref; ref = next) { + next = ref->next; + psx_do_unregister(ref); + } +} + +/* + * Change the PSX sensitivity level. If the threads appear to have + * diverged in behavior, this can cause the library to notify the + * user. + */ +int psx_set_sensitivity(psx_sensitivity_t level) { + if (level < PSX_IGNORE || level > PSX_ERROR) { + errno = EINVAL; + return -1; + } + psx_lock(); + psx_tracker.sensitivity = level; + psx_unlock(); + return 0; +} 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..130f0cb3195 --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx.go @@ -0,0 +1,35 @@ +// +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 + +// 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) { + return syscall.AllThreadsSyscall(syscallnr, arg1, arg2, arg3) +} + +//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) { + 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..1f7513736ea --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx_cgo.go @@ -0,0 +1,91 @@ +// +build linux,cgo + +package psx // import "kernel.org/pub/linux/libs/security/libcap/psx" + +import ( + "runtime" + "sync" + "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))) +} + +var makeFatal sync.Once + +// forceFatal configures the psx_syscall mechanism to PSX_ERROR. +func forceFatal() { + makeFatal.Do(func() { + C.psx_set_sensitivity(C.PSX_ERROR) + }) +} + +//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) { + forceFatal() + // 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) { + forceFatal() + // 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..7a8c9a19ea2 --- /dev/null +++ b/vendor/kernel.org/pub/linux/libs/security/libcap/psx/psx_syscall.h @@ -0,0 +1,95 @@ +/* + * 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 usage. + */ +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)); + +/* + * psx_sensitivity_t holds the level of paranoia for non-POSIX syscall + * behavior. The default is PSX_IGNORE: which is best effort - no + * enforcement; PSX_WARNING will dump to stderr a warning when a + * syscall's results differ; PSX_ERROR will dump info as per + * PSX_WARNING and generate a SIGSYS. The current mode can be set with + * psx_set_sensitivity(). + */ +typedef enum { + PSX_IGNORE = 0, + PSX_WARNING = 1, + PSX_ERROR = 2, +} psx_sensitivity_t; + +/* + * psx_set_sensitivity sets the current sensitivity of the PSX + * mechanism. The function returns 0 on success and -1 if the + * requested level is invalid. + */ +int psx_set_sensitivity(psx_sensitivity_t level); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_PSX_SYSCALL_H */ diff --git a/vendor/modules.txt b/vendor/modules.txt index 56c8177ebbc..6e3dc7d11de 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-20250303204525-1544bccde3a3 +## explicit; go 1.18 +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 @@ -127,3 +131,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.70 +## explicit; go 1.11 +kernel.org/pub/linux/libs/security/libcap/psx