Skip to content

Commit e9341f2

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 9a0419b commit e9341f2

File tree

9 files changed

+249
-3
lines changed

9 files changed

+249
-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
@@ -81,6 +81,53 @@ type Syscall struct {
8181
Args []*Arg `json:"args"`
8282
}
8383

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

@@ -209,6 +256,10 @@ type Config struct {
209256
// RootlessCgroups is set when unlikely to have the full access to cgroups.
210257
// When RootlessCgroups is set, cgroups errors are ignored.
211258
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
259+
260+
// Landlock specifies the Landlock unprivileged access control settings for the container process.
261+
// `noNewPrivileges` must be enabled to use Landlock.
262+
Landlock *Landlock `json:"landlock,omitempty"`
212263
}
213264

214265
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
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/opencontainers/runc/libcontainer/apparmor"
1515
"github.com/opencontainers/runc/libcontainer/keys"
16+
"github.com/opencontainers/runc/libcontainer/landlock"
1617
"github.com/opencontainers/runc/libcontainer/seccomp"
1718
"github.com/opencontainers/runc/libcontainer/system"
1819
)
@@ -86,6 +87,12 @@ func (l *linuxSetnsInit) Init() error {
8687
if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil {
8788
return err
8889
}
90+
// `noNewPrivileges` must be enabled to use Landlock.
91+
if l.config.Config.Landlock != nil && l.config.NoNewPrivileges {
92+
if err := landlock.InitLandlock(l.config.Config.Landlock); err != nil {
93+
return fmt.Errorf("unable to init Landlock: %w", err)
94+
}
95+
}
8996
// Set seccomp as close to execve as possible, so as few syscalls take
9097
// place afterward (reducing the amount of syscalls that users need to
9198
// enable in their seccomp profiles).

libcontainer/specconv/spec_linux.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/opencontainers/runc/libcontainer/cgroups"
1717
"github.com/opencontainers/runc/libcontainer/configs"
1818
"github.com/opencontainers/runc/libcontainer/devices"
19+
"github.com/opencontainers/runc/libcontainer/landlock"
1920
"github.com/opencontainers/runc/libcontainer/seccomp"
2021
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
2122
"github.com/opencontainers/runtime-spec/specs-go"
@@ -319,6 +320,14 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
319320
Ambient: spec.Process.Capabilities.Ambient,
320321
}
321322
}
323+
if spec.Process.Landlock != nil {
324+
landlock, err := SetupLandlock(spec.Process.Landlock)
325+
if err != nil {
326+
return nil, err
327+
}
328+
config.Landlock = landlock
329+
}
330+
322331
}
323332
createHooks(spec, config)
324333
config.Version = specs.Version
@@ -845,6 +854,55 @@ func parseMountOptions(options []string) (int, []int, string, int) {
845854
return flag, pgflag, strings.Join(data, ","), extFlags
846855
}
847856

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

libcontainer/standard_init_linux.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/opencontainers/runc/libcontainer/apparmor"
1717
"github.com/opencontainers/runc/libcontainer/configs"
1818
"github.com/opencontainers/runc/libcontainer/keys"
19+
"github.com/opencontainers/runc/libcontainer/landlock"
1920
"github.com/opencontainers/runc/libcontainer/seccomp"
2021
"github.com/opencontainers/runc/libcontainer/system"
2122
)
@@ -231,6 +232,13 @@ func (l *linuxStandardInit) Init() error {
231232
// https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1290-L1318
232233
_ = unix.Close(l.fifoFd)
233234

235+
// `noNewPrivileges` must be enabled to use Landlock.
236+
if l.config.Config.Landlock != nil && l.config.NoNewPrivileges {
237+
if err := landlock.InitLandlock(l.config.Config.Landlock); err != nil {
238+
return fmt.Errorf("unable to init Landlock: %w", err)
239+
}
240+
}
241+
234242
s := l.config.SpecState
235243
s.Pid = unix.Getpid()
236244
s.Status = specs.StateCreated

0 commit comments

Comments
 (0)