Skip to content

Commit f99455d

Browse files
committed
libcontainer: add support for Landlock
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 <[email protected]>
1 parent 654f331 commit f99455d

File tree

9 files changed

+250
-3
lines changed

9 files changed

+250
-3
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/cyphar/filepath-securejoin v0.2.3
1212
github.com/docker/go-units v0.4.0
1313
github.com/godbus/dbus/v5 v5.0.4
14+
github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946
1415
github.com/moby/sys/mountinfo v0.4.1
1516
github.com/mrunalp/fileutils v0.5.0
1617
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
@@ -22,6 +23,6 @@ require (
2223
github.com/urfave/cli v1.22.1
2324
github.com/vishvananda/netlink v1.1.0
2425
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
25-
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887
26+
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf
2627
google.golang.org/protobuf v1.27.1
2728
)

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
4040
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
4141
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
4242
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
43+
github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946 h1:RRTOwBnwZR4a3IMyPq1uchxJcrLKWF4NTCHB2fbvo5Y=
44+
github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946/go.mod h1:wjznJ04q4Tvsbx3vkzfmgfEOe6w5dSGlXFa+xbSl9X8=
4345
github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM=
4446
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
4547
github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4=
@@ -76,8 +78,8 @@ golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7w
7678
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7779
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7880
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
79-
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs=
80-
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
81+
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
82+
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8183
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
8284
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
8385
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -94,3 +96,5 @@ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+Rur
9496
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
9597
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9698
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
99+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.51 h1:VXVXjnTUsA9zeHIolNb6moSXZavDe1pD8Q0lPXZEOwc=
100+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.51/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=

libcontainer/configs/config.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,53 @@ type Syscall struct {
7878
Args []*Arg `json:"args"`
7979
}
8080

81+
// Landlock specifies the Landlock unprivileged access control settings for the container process.
82+
type Landlock struct {
83+
Ruleset *Ruleset `json:"ruleset"`
84+
Rules *Rules `json:"rules"`
85+
DisableBestEffort bool `json:"disableBestEffort"`
86+
}
87+
88+
// Ruleset identifies a set of rules (i.e., actions on objects) that need to be handled in Landlock.
89+
type Ruleset struct {
90+
HandledAccessFS AccessFS `json:"handledAccessFS"`
91+
}
92+
93+
// Rules represents the security policies (i.e., actions allowed on objects) in Landlock.
94+
type Rules struct {
95+
PathBeneath []*RulePathBeneath `json:"pathBeneath"`
96+
}
97+
98+
// RulePathBeneath defines the file-hierarchy typed rule that grants the access rights specified by
99+
// `AllowedAccess` to the file hierarchies under the given `Paths` in Landlock.
100+
type RulePathBeneath struct {
101+
AllowedAccess AccessFS `json:"allowedAccess"`
102+
Paths []string `json:"paths"`
103+
}
104+
105+
// AccessFS is taken upon ruleset and rule setup in Landlock.
106+
type AccessFS uint64
107+
108+
// Landlock access rights for FS.
109+
//
110+
// Please see the full documentation at
111+
// https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights.
112+
const (
113+
Execute AccessFS = (1 << 0)
114+
WriteFile AccessFS = (1 << 1)
115+
ReadFile AccessFS = (1 << 2)
116+
ReadDir AccessFS = (1 << 3)
117+
RemoveDir AccessFS = (1 << 4)
118+
RemoveFile AccessFS = (1 << 5)
119+
MakeChar AccessFS = (1 << 6)
120+
MakeDir AccessFS = (1 << 7)
121+
MakeReg AccessFS = (1 << 8)
122+
MakeSock AccessFS = (1 << 9)
123+
MakeFifo AccessFS = (1 << 10)
124+
MakeBlock AccessFS = (1 << 11)
125+
MakeSym AccessFS = (1 << 12)
126+
)
127+
81128
// TODO Windows. Many of these fields should be factored out into those parts
82129
// which are common across platforms, and those which are platform specific.
83130

@@ -206,6 +253,10 @@ type Config struct {
206253
// RootlessCgroups is set when unlikely to have the full access to cgroups.
207254
// When RootlessCgroups is set, cgroups errors are ignored.
208255
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
256+
257+
// Landlock specifies the Landlock unprivileged access control settings for the container process.
258+
// `noNewPrivileges` must be enabled to use Landlock.
259+
Landlock *Landlock `json:"landlock,omitempty"`
209260
}
210261

211262
type (

libcontainer/landlock/config.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package landlock
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/opencontainers/runc/libcontainer/configs"
7+
)
8+
9+
var accessFSs = map[string]configs.AccessFS{
10+
"execute": configs.Execute,
11+
"write_file": configs.WriteFile,
12+
"read_file": configs.ReadFile,
13+
"read_dir": configs.ReadDir,
14+
"remove_dir": configs.RemoveDir,
15+
"remove_file": configs.RemoveFile,
16+
"make_char": configs.MakeChar,
17+
"make_dir": configs.MakeDir,
18+
"make_reg": configs.MakeReg,
19+
"make_sock": configs.MakeSock,
20+
"make_fifo": configs.MakeFifo,
21+
"make_block": configs.MakeBlock,
22+
"make_sym": configs.MakeSym,
23+
}
24+
25+
// ConvertStringToAccessFS converts a string into a Landlock access right.
26+
func ConvertStringToAccessFS(in string) (configs.AccessFS, error) {
27+
if access, ok := accessFSs[in]; ok {
28+
return access, nil
29+
}
30+
return 0, fmt.Errorf("string %s is not a valid access right for landlock", in)
31+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// +build linux
2+
3+
package landlock
4+
5+
import (
6+
"errors"
7+
"fmt"
8+
9+
"github.com/landlock-lsm/go-landlock/landlock"
10+
11+
"github.com/opencontainers/runc/libcontainer/configs"
12+
)
13+
14+
// Initialize Landlock unprivileged access control for the container process
15+
// based on the given settings.
16+
// The specified `ruleset` identifies a set of rules (i.e., actions on objects)
17+
// that need to be handled (i.e., restricted) by Landlock. And if no `rule`
18+
// explicitly allow them, they should then be forbidden.
19+
// The `disableBestEffort` input gives control over whether the best-effort
20+
// security approach should be applied for Landlock access rights.
21+
func InitLandlock(config *configs.Landlock) error {
22+
if config == nil {
23+
return errors.New("cannot initialize Landlock - nil config passed")
24+
}
25+
26+
var llConfig landlock.Config
27+
28+
ruleset := getAccess(config.Ruleset.HandledAccessFS)
29+
// Panic on error when constructing the Landlock configuration using invalid config values.
30+
if config.DisableBestEffort {
31+
llConfig = landlock.MustConfig(ruleset)
32+
} else {
33+
llConfig = landlock.MustConfig(ruleset).BestEffort()
34+
}
35+
36+
if err := llConfig.RestrictPaths(
37+
getPathAccesses(config.Rules)...,
38+
); err != nil {
39+
return fmt.Errorf("Could not restrict paths: %v", err)
40+
}
41+
42+
return nil
43+
}
44+
45+
// Convert Libcontainer AccessFS to go-landlock AccessFSSet.
46+
func getAccess(access configs.AccessFS) landlock.AccessFSSet {
47+
return landlock.AccessFSSet(access)
48+
}
49+
50+
// Convert Libcontainer RulePathBeneath to go-landlock PathOpt.
51+
func getPathAccess(rule *configs.RulePathBeneath) landlock.PathOpt {
52+
return landlock.PathAccess(
53+
getAccess(rule.AllowedAccess),
54+
rule.Paths...)
55+
}
56+
57+
// Convert Libcontainer Rules to an array of go-landlock PathOpt.
58+
func getPathAccesses(rules *configs.Rules) []landlock.PathOpt {
59+
pathAccesses := []landlock.PathOpt{}
60+
61+
for _, rule := range rules.PathBeneath {
62+
opt := getPathAccess(rule)
63+
pathAccesses = append(pathAccesses, opt)
64+
}
65+
66+
return pathAccesses
67+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// +build !linux
2+
3+
package landlock
4+
5+
import (
6+
"errors"
7+
8+
"github.com/opencontainers/runc/libcontainer/configs"
9+
)
10+
11+
var ErrLandlockNotSupported = errors.New("land: config provided but Landlock not supported")
12+
13+
// InitLandlock does nothing because Landlock is not supported.
14+
func InitSLandlock(config *configs.Landlock) error {
15+
if config != nil {
16+
return ErrLandlockNotSupported
17+
}
18+
return nil
19+
}

libcontainer/setns_init_linux.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/opencontainers/runc/libcontainer/apparmor"
1717
"github.com/opencontainers/runc/libcontainer/keys"
18+
"github.com/opencontainers/runc/libcontainer/landlock"
1819
"github.com/opencontainers/runc/libcontainer/seccomp"
1920
"github.com/opencontainers/runc/libcontainer/system"
2021
)
@@ -83,6 +84,12 @@ func (l *linuxSetnsInit) Init() error {
8384
if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil {
8485
return err
8586
}
87+
// `noNewPrivileges` must be enabled to use Landlock.
88+
if l.config.Config.Landlock != nil && l.config.NoNewPrivileges {
89+
if err := landlock.InitLandlock(l.config.Config.Landlock); err != nil {
90+
return fmt.Errorf("unable to init Landlock: %w", err)
91+
}
92+
}
8693
// Set seccomp as close to execve as possible, so as few syscalls take
8794
// place afterward (reducing the amount of syscalls that users need to
8895
// enable in their seccomp profiles).

libcontainer/specconv/spec_linux.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/opencontainers/runc/libcontainer/cgroups"
1919
"github.com/opencontainers/runc/libcontainer/configs"
2020
"github.com/opencontainers/runc/libcontainer/devices"
21+
"github.com/opencontainers/runc/libcontainer/landlock"
2122
"github.com/opencontainers/runc/libcontainer/seccomp"
2223
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
2324
"github.com/opencontainers/runtime-spec/specs-go"
@@ -320,6 +321,14 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
320321
Ambient: spec.Process.Capabilities.Ambient,
321322
}
322323
}
324+
if spec.Process.Landlock != nil {
325+
landlock, err := SetupLandlock(spec.Process.Landlock)
326+
if err != nil {
327+
return nil, err
328+
}
329+
config.Landlock = landlock
330+
}
331+
323332
}
324333
createHooks(spec, config)
325334
config.Version = specs.Version
@@ -837,6 +846,55 @@ func parseMountOptions(options []string) (int, []int, string, int) {
837846
return flag, pgflag, strings.Join(data, ","), extFlags
838847
}
839848

849+
func SetupLandlock(ll *specs.Landlock) (*configs.Landlock, error) {
850+
if ll == nil {
851+
return nil, nil
852+
}
853+
854+
// No ruleset specified, assume landlock disabled.
855+
if ll.Ruleset == nil || len(ll.Ruleset.HandledAccessFS) == 0 {
856+
return nil, nil
857+
}
858+
859+
newConfig := &configs.Landlock{
860+
Ruleset: new(configs.Ruleset),
861+
Rules: &configs.Rules{
862+
PathBeneath: []*configs.RulePathBeneath{},
863+
},
864+
DisableBestEffort: ll.DisableBestEffort,
865+
}
866+
867+
for _, access := range ll.Ruleset.HandledAccessFS {
868+
newAccessFs, err := landlock.ConvertStringToAccessFS(string(access))
869+
if err != nil {
870+
return nil, err
871+
}
872+
newConfig.Ruleset.HandledAccessFS |= newAccessFs
873+
}
874+
875+
// Loop through all Landlock path beneath rule blocks and convert them to libcontainer format.
876+
for _, rulePath := range ll.Rules.PathBeneath {
877+
if len(rulePath.AllowedAccess) > 0 {
878+
newRule := configs.RulePathBeneath{
879+
AllowedAccess: 0,
880+
Paths: rulePath.Paths,
881+
}
882+
883+
for _, access := range rulePath.AllowedAccess {
884+
newAllowedAccess, err := landlock.ConvertStringToAccessFS(string(access))
885+
if err != nil {
886+
return nil, err
887+
}
888+
newRule.AllowedAccess |= newAllowedAccess
889+
}
890+
891+
newConfig.Rules.PathBeneath = append(newConfig.Rules.PathBeneath, &newRule)
892+
}
893+
}
894+
895+
return newConfig, nil
896+
}
897+
840898
func SetupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) {
841899
if config == nil {
842900
return nil, nil

libcontainer/standard_init_linux.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/opencontainers/runc/libcontainer/apparmor"
1919
"github.com/opencontainers/runc/libcontainer/configs"
2020
"github.com/opencontainers/runc/libcontainer/keys"
21+
"github.com/opencontainers/runc/libcontainer/landlock"
2122
"github.com/opencontainers/runc/libcontainer/seccomp"
2223
"github.com/opencontainers/runc/libcontainer/system"
2324
)
@@ -211,6 +212,14 @@ func (l *linuxStandardInit) Init() error {
211212
// since been resolved.
212213
// https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1290-L1318
213214
_ = unix.Close(l.fifoFd)
215+
216+
// `noNewPrivileges` must be enabled to use Landlock.
217+
if l.config.Config.Landlock != nil && l.config.NoNewPrivileges {
218+
if err := landlock.InitLandlock(l.config.Config.Landlock); err != nil {
219+
return fmt.Errorf("unable to init Landlock: %w", err)
220+
}
221+
}
222+
214223
// Set seccomp as close to execve as possible, so as few syscalls take
215224
// place afterward (reducing the amount of syscalls that users need to
216225
// enable in their seccomp profiles).

0 commit comments

Comments
 (0)