Skip to content

Commit 4bac159

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]> Signed-off-by: Manjusaka <[email protected]>
1 parent 2e906e2 commit 4bac159

File tree

9 files changed

+312
-0
lines changed

9 files changed

+312
-0
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ require (
3333

3434
require (
3535
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
36+
github.com/landlock-lsm/go-landlock v0.0.0-20210828133255-ec6c6b87a946
3637
github.com/russross/blackfriday/v2 v2.1.0 // indirect
3738
github.com/vishvananda/netns v0.0.4 // indirect
39+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.51 // indirect
3840
)

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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/sirupsen/logrus"
1111
"golang.org/x/sys/unix"
1212

13+
"github.com/landlock-lsm/go-landlock/landlock"
1314
"github.com/opencontainers/runc/libcontainer/devices"
1415
"github.com/opencontainers/runtime-spec/specs-go"
1516
)
@@ -85,6 +86,33 @@ type Syscall struct {
8586
Args []*Arg `json:"args"`
8687
}
8788

89+
// Landlock specifies the Landlock unprivileged access control settings for the container process.
90+
type Landlock struct {
91+
Ruleset *Ruleset `json:"ruleset"`
92+
Rules *Rules `json:"rules"`
93+
DisableBestEffort bool `json:"disableBestEffort"`
94+
}
95+
96+
// Ruleset identifies a set of rules (i.e., actions on objects) that need to be handled in Landlock.
97+
type Ruleset struct {
98+
HandledAccessFS landlock.AccessFSSet `json:"handledAccessFS"`
99+
}
100+
101+
// Rules represents the security policies (i.e., actions allowed on objects) in Landlock.
102+
type Rules struct {
103+
PathBeneath []*RulePathBeneath `json:"pathBeneath"`
104+
}
105+
106+
// RulePathBeneath defines the file-hierarchy typed rule that grants the access rights specified by
107+
// AllowedAccess to the file hierarchies under the given Paths in Landlock.
108+
type RulePathBeneath struct {
109+
AllowedAccess landlock.AccessFSSet `json:"allowedAccess"`
110+
Paths []string `json:"paths"`
111+
}
112+
113+
// TODO Windows. Many of these fields should be factored out into those parts
114+
// which are common across platforms, and those which are platform specific.
115+
88116
// Config defines configuration options for executing a process inside a contained environment.
89117
type Config struct {
90118
// NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs
@@ -225,6 +253,9 @@ type Config struct {
225253

226254
// IOPriority is the container's I/O priority.
227255
IOPriority *IOPriority `json:"io_priority,omitempty"`
256+
// Landlock specifies the Landlock unprivileged access control settings for the container process.
257+
// NoNewPrivileges must be enabled to use Landlock.
258+
Landlock *Landlock `json:"landlock,omitempty"`
228259
}
229260

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

libcontainer/landlock/config.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package landlock
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/landlock-lsm/go-landlock/landlock"
7+
ll "github.com/landlock-lsm/go-landlock/landlock/syscall"
8+
)
9+
10+
var accessFSSets = map[string]landlock.AccessFSSet{
11+
"execute": ll.AccessFSExecute,
12+
"write_file": ll.AccessFSWriteFile,
13+
"read_file": ll.AccessFSReadFile,
14+
"read_dir": ll.AccessFSReadDir,
15+
"remove_dir": ll.AccessFSRemoveDir,
16+
"remove_file": ll.AccessFSRemoveFile,
17+
"make_char": ll.AccessFSMakeChar,
18+
"make_dir": ll.AccessFSMakeDir,
19+
"make_reg": ll.AccessFSMakeReg,
20+
"make_sock": ll.AccessFSMakeSock,
21+
"make_fifo": ll.AccessFSMakeFifo,
22+
"make_block": ll.AccessFSMakeBlock,
23+
"make_sym": ll.AccessFSMakeSym,
24+
}
25+
26+
// ConvertStringToAccessFSSet converts a string into a go-landlock AccessFSSet
27+
// access right.
28+
// This gives more explicit control over the mapping between the permitted
29+
// values in the spec and the ones supported in go-landlock library.
30+
func ConvertStringToAccessFSSet(in string) (landlock.AccessFSSet, error) {
31+
if access, ok := accessFSSets[in]; ok {
32+
return access, nil
33+
}
34+
return 0, fmt.Errorf("string %s is not a valid access right for landlock", in)
35+
}

libcontainer/landlock/landlock.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package landlock
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/landlock-lsm/go-landlock/landlock"
8+
9+
"github.com/opencontainers/runc/libcontainer/configs"
10+
)
11+
12+
// Initialize Landlock unprivileged access control for the container process
13+
// based on the given settings.
14+
// The specified `ruleset` identifies a set of rules (i.e., actions on objects)
15+
// that need to be handled (i.e., restricted) by Landlock. And if no `rule`
16+
// explicitly allow them, they should then be forbidden.
17+
// The `disableBestEffort` input gives control over whether the best-effort
18+
// security approach should be applied for Landlock access rights.
19+
func InitLandlock(config *configs.Landlock) error {
20+
if config == nil {
21+
return errors.New("cannot initialize Landlock - nil config passed")
22+
}
23+
24+
ruleset := config.Ruleset.HandledAccessFS
25+
llConfig, err := landlock.NewConfig(ruleset)
26+
if err != nil {
27+
return fmt.Errorf("could not create ruleset: %w", err)
28+
}
29+
30+
if !config.DisableBestEffort {
31+
*llConfig = llConfig.BestEffort()
32+
}
33+
34+
if err := llConfig.RestrictPaths(
35+
pathAccesses(config.Rules)...,
36+
); err != nil {
37+
return fmt.Errorf("could not restrict paths: %w", err)
38+
}
39+
40+
return nil
41+
}
42+
43+
// Convert Libcontainer RulePathBeneath to go-landlock PathOpt.
44+
func pathAccess(rule *configs.RulePathBeneath) landlock.PathOpt {
45+
return landlock.PathAccess(rule.AllowedAccess, rule.Paths...)
46+
}
47+
48+
// Convert Libcontainer Rules to an array of go-landlock PathOpt.
49+
func pathAccesses(rules *configs.Rules) []landlock.PathOpt {
50+
pathAccesses := []landlock.PathOpt{}
51+
52+
for _, rule := range rules.PathBeneath {
53+
opt := pathAccess(rule)
54+
pathAccesses = append(pathAccesses, opt)
55+
}
56+
57+
return pathAccesses
58+
}

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: 63 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,19 @@ 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+
568+
landlock, err := SetupLandlock(spec.Process.Landlock)
569+
if err != nil {
570+
return nil, err
571+
}
572+
config.Landlock = landlock
559573
}
560574
createHooks(spec, config)
561575
config.Version = specs.Version
@@ -1135,6 +1149,55 @@ func parseMountOptions(options []string) *configs.Mount {
11351149
return &m
11361150
}
11371151

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

0 commit comments

Comments
 (0)