libcontainer: add support for Landlock#3194
libcontainer: add support for Landlock#3194kailun-qin wants to merge 5 commits intoopencontainers:mainfrom
Conversation
libcontainer/standard_init_linux.go
Outdated
| // https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1290-L1318 | ||
| _ = unix.Close(l.fifoFd) | ||
|
|
||
| // `noNewPrivileges` must be enabled to use Landlock. |
There was a problem hiding this comment.
Please do not use github markdown in comments.
There was a problem hiding this comment.
Thanks, removed markdown.
libcontainer/configs/config.go
Outdated
| } | ||
|
|
||
| // RulePathBeneath defines the file-hierarchy typed rule that grants the access rights specified by | ||
| // `AllowedAccess` to the file hierarchies under the given `Paths` in Landlock. |
There was a problem hiding this comment.
Please do not use github markdown in comments.
There was a problem hiding this comment.
Thanks, removed markdown.
libcontainer/configs/config.go
Outdated
| const ( | ||
| Execute AccessFS = (1 << 0) | ||
| WriteFile AccessFS = (1 << 1) | ||
| ReadFile AccessFS = (1 << 2) | ||
| ReadDir AccessFS = (1 << 3) | ||
| RemoveDir AccessFS = (1 << 4) | ||
| RemoveFile AccessFS = (1 << 5) | ||
| MakeChar AccessFS = (1 << 6) | ||
| MakeDir AccessFS = (1 << 7) | ||
| MakeReg AccessFS = (1 << 8) | ||
| MakeSock AccessFS = (1 << 9) | ||
| MakeFifo AccessFS = (1 << 10) | ||
| MakeBlock AccessFS = (1 << 11) | ||
| MakeSym AccessFS = (1 << 12) | ||
| ) |
There was a problem hiding this comment.
Rather than a copy/paste from https://github.com/landlock-lsm/go-landlock/blob/main/landlock/syscall/landlock.go maybe it can be imported, and the constants are used directly?
There was a problem hiding this comment.
Thanks, use built-in consts from go-landlock directly so remove these.
libcontainer/landlock/config.go
Outdated
| var accessFSs = map[string]configs.AccessFS{ | ||
| "execute": configs.Execute, | ||
| "write_file": configs.WriteFile, | ||
| "read_file": configs.ReadFile, | ||
| "read_dir": configs.ReadDir, | ||
| "remove_dir": configs.RemoveDir, | ||
| "remove_file": configs.RemoveFile, | ||
| "make_char": configs.MakeChar, | ||
| "make_dir": configs.MakeDir, | ||
| "make_reg": configs.MakeReg, | ||
| "make_sock": configs.MakeSock, | ||
| "make_fifo": configs.MakeFifo, | ||
| "make_block": configs.MakeBlock, | ||
| "make_sym": configs.MakeSym, | ||
| } | ||
|
|
||
| // ConvertStringToAccessFS converts a string into a Landlock access right. | ||
| func ConvertStringToAccessFS(in string) (configs.AccessFS, error) { | ||
| if access, ok := accessFSs[in]; ok { | ||
| return access, nil | ||
| } | ||
| return 0, fmt.Errorf("string %s is not a valid access right for landlock", in) | ||
| } |
There was a problem hiding this comment.
Would be nice to have this functionality in https://github.com/landlock-lsm/go-landlock/blob/main/landlock/accessfs.go and use it.
There was a problem hiding this comment.
Right, I got this same concern here. So still keep at this moment and update with more comments.
There was a problem hiding this comment.
Feel free to create a PR for go-landlock 😉
There was a problem hiding this comment.
As I mentioned also in landlock-lsm/go-landlock#14, please be aware that when this string-to-enum mapping is part of go-landlock, the set of supported values might grow at some point in the future when you update the go-landlock library, and this might change the permitted values in your configuration language implicitly.
If this is the approach you want to take, and since @l0kod seems to also be on board, we could make that mapping part of the library, in the lower-case, underscore-delimited variant ("write_file")
There was a problem hiding this comment.
It may help to have an helper to check that an access right is supported by a specific ABI version, or specify the version as an argument. I'm not sure if the string conversion belongs to go-landlock or libcontainer though. Is libcontainer used by other container runtimes?
There was a problem hiding this comment.
It should be usable for other container runtimes but I'm not sure here. @kolyshkin @AkihiroSuda @cyphar Any insights? Thanks!
| // Convert Libcontainer AccessFS to go-landlock AccessFSSet. | ||
| func getAccess(access configs.AccessFS) landlock.AccessFSSet { | ||
| return landlock.AccessFSSet(access) | ||
| } | ||
|
|
There was a problem hiding this comment.
Why not use landlock.AccessFSSet type directly?
There was a problem hiding this comment.
The original intention was to maintain a configs.AccessFS to have a decoupled layer between specs and runc, as well as runc and go-landlock, in case that an AccessFS is supported in one place not the other.
Rethink over this again, I think using landlock.AccessFSSet can achieve the above also, as long as we have explicit control/convert in runc (partially discussed here https://github.com/opencontainers/runc/pull/3194/files#r700925526).
| } | ||
|
|
||
| // Convert Libcontainer RulePathBeneath to go-landlock PathOpt. | ||
| func getPathAccess(rule *configs.RulePathBeneath) landlock.PathOpt { |
There was a problem hiding this comment.
Prefixes such as get are discouraged by "effective go", see https://golang.org/doc/effective_go#Getters
IOW getPathAccess -> pathAccess.
Same for other getXxx... functions
| // +build !linux | ||
|
|
||
| package landlock | ||
|
|
||
| import ( | ||
| "errors" | ||
|
|
||
| "github.com/opencontainers/runc/libcontainer/configs" | ||
| ) | ||
|
|
||
| var ErrLandlockNotSupported = errors.New("land: config provided but Landlock not supported") | ||
|
|
||
| // InitLandlock does nothing because Landlock is not supported. | ||
| func InitSLandlock(config *configs.Landlock) error { | ||
| if config != nil { | ||
| return ErrLandlockNotSupported | ||
| } | ||
| return nil | ||
| } |
There was a problem hiding this comment.
runc is linux-only, I guess you can drop this entirely (as well as _linux suffix from all the other files).
There was a problem hiding this comment.
Thanks, removed entirely!
libcontainer/specconv/spec_linux.go
Outdated
| Ambient: spec.Process.Capabilities.Ambient, | ||
| } | ||
| } | ||
| if spec.Process.Landlock != nil { |
There was a problem hiding this comment.
Since you have nil check in SetupLandlock already, this one seems redundant.
libcontainer/configs/config.go
Outdated
| } | ||
|
|
||
| // AccessFS is taken upon ruleset and rule setup in Landlock. | ||
| type AccessFS uint64 |
There was a problem hiding this comment.
Why not use landlock.AccessFS type directly (or, if you're so inclined to have a locally defined type, use something like type AccessFS=landlock.AccessFS so you won't need to convert).
There was a problem hiding this comment.
Thanks, updated to use landlock.AccessFSSet directly.
| var ErrLandlockNotSupported = errors.New("land: config provided but Landlock not supported") | ||
|
|
||
| // InitLandlock does nothing because Landlock is not supported. | ||
| func InitSLandlock(config *configs.Landlock) error { |
| if err := llConfig.RestrictPaths( | ||
| getPathAccesses(config.Rules)..., | ||
| ); err != nil { | ||
| return fmt.Errorf("Could not restrict paths: %v", err) |
There was a problem hiding this comment.
nit: error messages should not be Capitalized.
| "github.com/opencontainers/runc/libcontainer/configs" | ||
| ) | ||
|
|
||
| var ErrLandlockNotSupported = errors.New("land: config provided but Landlock not supported") |
|
Address comments and add unit tests. PTAL, thanks! |
| var llConfig landlock.Config | ||
|
|
||
| ruleset := getAccess(config.Ruleset.HandledAccessFS) | ||
| // Panic on error when constructing the Landlock configuration using invalid config values. |
There was a problem hiding this comment.
You can use landlock.NewConfig() instead to handle this more gracefully. (It returns the config and an error)
There was a problem hiding this comment.
Thanks, updated! More make sense to use NewConfig instead.
| llConfig = landlock.MustConfig(ruleset).BestEffort() | ||
| } | ||
|
|
||
| if err := llConfig.RestrictPaths( |
There was a problem hiding this comment.
I have today changed the semantics here slightly, so that the AccessFSSet passed as PathAccess() parameter needs to be a subset of the one configured in MustConfig(). The intention being that it should be forbidden to say "A and B are forbidden, but C is permitted on /tmp". That makes no sense to say, as C was never forbidden, so it's now treated as an error. You might want to catch that case at the configuration level already, otherwise users will get an error from RestrictPaths() if they misconfigure it in their config file.
Commit is here:
landlock-lsm/go-landlock@c567107
See bug landlock-lsm/go-landlock#18 for details.
Sorry about the slight API breakage, I'm hoping this is for the better in the long run, to be strict about the inputs.
0fa5201 to
075542f
Compare
This patch introduces Landlock Linux Security Module (LSM) support in runc, which was landed in Linux kernel 5.13. This allows unprivileged processes to create safe security sandboxes that can securely restrict the ambient rights (e.g. global filesystem access) for themselves. runtime-spec: opencontainers/runtime-spec#1111 Fixes opencontainers#2859 Signed-off-by: Kailun Qin <kailun.qin@intel.com>
* use landlock.AccessFSSet type directly * remove non-linux files * some minor updates Signed-off-by: Kailun Qin <kailun.qin@intel.com>
Signed-off-by: Kailun Qin <kailun.qin@intel.com>
Signed-off-by: Kailun Qin <kailun.qin@intel.com>
Signed-off-by: Kailun Qin <kailun.qin@intel.com>
|
Could you update this PR with the latest revision of opencontainers/runtime-spec#1111 ? Thanks! |
|
ping @kailun-qin 🙏 |
|
@AkihiroSuda May I carry this PR? |
|
Needs rebase |
This patch introduces Landlock Linux Security Module (LSM) support in
runc, which was landed in Linux kernel 5.13.
This allows unprivileged processes to create safe security sandboxes
that can securely restrict the ambient rights (e.g. global filesystem
access) for themselves.
runtime-spec: opencontainers/runtime-spec#1111
Fixes #2859
Signed-off-by: Kailun Qin kailun.qin@intel.com