Skip to content

Commit 71d23f1

Browse files
kailun-qinZheaoli
andcommitted
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 Co-authored-by: Zheao Li <[email protected]> Signed-off-by: Kailun Qin <[email protected]>
1 parent 2e906e2 commit 71d23f1

File tree

9 files changed

+251
-0
lines changed

9 files changed

+251
-0
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ require (
3131
google.golang.org/protobuf v1.36.0
3232
)
3333

34+
require kernel.org/pub/linux/libs/security/libcap/psx v1.2.51 // indirect
35+
3436
require (
3537
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
38+
github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946
3639
github.com/russross/blackfriday/v2 v2.1.0 // indirect
3740
github.com/vishvananda/netns v0.0.4 // indirect
3841
)

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
3535
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
3636
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
3737
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
38+
github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946 h1:RRTOwBnwZR4a3IMyPq1uchxJcrLKWF4NTCHB2fbvo5Y=
39+
github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946/go.mod h1:wjznJ04q4Tvsbx3vkzfmgfEOe6w5dSGlXFa+xbSl9X8=
3840
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
3941
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
4042
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
@@ -86,6 +88,7 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
8688
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
8789
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
8890
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
91+
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8992
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9093
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9194
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -102,3 +105,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
102105
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
103106
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
104107
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
108+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.51 h1:VXVXjnTUsA9zeHIolNb6moSXZavDe1pD8Q0lPXZEOwc=
109+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.51/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=

libcontainer/configs/config.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,56 @@ type Syscall struct {
8585
Args []*Arg `json:"args"`
8686
}
8787

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

226276
// IOPriority is the container's I/O priority.
227277
IOPriority *IOPriority `json:"io_priority,omitempty"`
278+
// Landlock specifies the Landlock unprivileged access control settings for the container process.
279+
// `noNewPrivileges` must be enabled to use Landlock.
280+
Landlock *Landlock `json:"landlock,omitempty"`
228281
}
229282

230283
// Scheduler is based on the Linux sched_setattr(2) syscall.

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
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/opencontainers/runc/libcontainer/apparmor"
1414
"github.com/opencontainers/runc/libcontainer/keys"
15+
"github.com/opencontainers/runc/libcontainer/landlock"
1516
"github.com/opencontainers/runc/libcontainer/seccomp"
1617
"github.com/opencontainers/runc/libcontainer/system"
1718
"github.com/opencontainers/runc/libcontainer/utils"
@@ -116,6 +117,12 @@ func (l *linuxSetnsInit) Init() error {
116117
if err != nil {
117118
return err
118119
}
120+
// `noNewPrivileges` must be enabled to use Landlock.
121+
if l.config.Config.Landlock != nil && l.config.NoNewPrivileges {
122+
if err := landlock.InitLandlock(l.config.Config.Landlock); err != nil {
123+
return fmt.Errorf("unable to init Landlock: %w", err)
124+
}
125+
}
119126
// Set seccomp as close to execve as possible, so as few syscalls take
120127
// place afterward (reducing the amount of syscalls that users need to
121128
// 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/configs"
1919
"github.com/opencontainers/runc/libcontainer/devices"
2020
"github.com/opencontainers/runc/libcontainer/internal/userns"
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"
@@ -556,6 +557,14 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
556557
ioPriority := *spec.Process.IOPriority
557558
config.IOPriority = &ioPriority
558559
}
560+
if spec.Process.Landlock != nil {
561+
landlock, err := SetupLandlock(spec.Process.Landlock)
562+
if err != nil {
563+
return nil, err
564+
}
565+
config.Landlock = landlock
566+
}
567+
559568
}
560569
createHooks(spec, config)
561570
config.Version = specs.Version
@@ -1135,6 +1144,55 @@ func parseMountOptions(options []string) *configs.Mount {
11351144
return &m
11361145
}
11371146

1147+
func SetupLandlock(ll *specs.Landlock) (*configs.Landlock, error) {
1148+
if ll == nil {
1149+
return nil, nil
1150+
}
1151+
1152+
// No ruleset specified, assume landlock disabled.
1153+
if ll.Ruleset == nil || len(ll.Ruleset.HandledAccessFS) == 0 {
1154+
return nil, nil
1155+
}
1156+
1157+
newConfig := &configs.Landlock{
1158+
Ruleset: new(configs.Ruleset),
1159+
Rules: &configs.Rules{
1160+
PathBeneath: []*configs.RulePathBeneath{},
1161+
},
1162+
DisableBestEffort: ll.DisableBestEffort,
1163+
}
1164+
1165+
for _, access := range ll.Ruleset.HandledAccessFS {
1166+
newAccessFs, err := landlock.ConvertStringToAccessFS(string(access))
1167+
if err != nil {
1168+
return nil, err
1169+
}
1170+
newConfig.Ruleset.HandledAccessFS |= newAccessFs
1171+
}
1172+
1173+
// Loop through all Landlock path beneath rule blocks and convert them to libcontainer format.
1174+
for _, rulePath := range ll.Rules.PathBeneath {
1175+
if len(rulePath.AllowedAccess) > 0 {
1176+
newRule := configs.RulePathBeneath{
1177+
AllowedAccess: 0,
1178+
Paths: rulePath.Paths,
1179+
}
1180+
1181+
for _, access := range rulePath.AllowedAccess {
1182+
newAllowedAccess, err := landlock.ConvertStringToAccessFS(string(access))
1183+
if err != nil {
1184+
return nil, err
1185+
}
1186+
newRule.AllowedAccess |= newAllowedAccess
1187+
}
1188+
1189+
newConfig.Rules.PathBeneath = append(newConfig.Rules.PathBeneath, &newRule)
1190+
}
1191+
}
1192+
1193+
return newConfig, nil
1194+
}
1195+
11381196
func SetupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) {
11391197
if config == nil {
11401198
return nil, nil

libcontainer/standard_init_linux.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/opencontainers/runc/libcontainer/apparmor"
1515
"github.com/opencontainers/runc/libcontainer/configs"
1616
"github.com/opencontainers/runc/libcontainer/keys"
17+
"github.com/opencontainers/runc/libcontainer/landlock"
1718
"github.com/opencontainers/runc/libcontainer/seccomp"
1819
"github.com/opencontainers/runc/libcontainer/system"
1920
"github.com/opencontainers/runc/libcontainer/utils"
@@ -267,6 +268,13 @@ func (l *linuxStandardInit) Init() error {
267268
// https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1290-L1318
268269
_ = l.fifoFile.Close()
269270

271+
// `noNewPrivileges` must be enabled to use Landlock.
272+
if l.config.Config.Landlock != nil && l.config.NoNewPrivileges {
273+
if err := landlock.InitLandlock(l.config.Config.Landlock); err != nil {
274+
return fmt.Errorf("unable to init Landlock: %w", err)
275+
}
276+
}
277+
270278
s := l.config.SpecState
271279
s.Pid = unix.Getpid()
272280
s.Status = specs.StateCreated

0 commit comments

Comments
 (0)