Skip to content

Commit 8b47a24

Browse files
Merge pull request #1529 from giuseppe/rootless-improvements
Support multiple users/groups mapped for the rootless case
2 parents 13fa5d2 + eb5bd4f commit 8b47a24

File tree

19 files changed

+406
-126
lines changed

19 files changed

+406
-126
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ RUN apt-get update && apt-get install -y \
2222
protobuf-c-compiler \
2323
protobuf-compiler \
2424
python-minimal \
25+
uidmap \
2526
--no-install-recommends \
2627
&& apt-get clean
2728

Makefile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,10 @@ localintegration: all
7777
bats -t tests/integration${TESTFLAGS}
7878

7979
rootlessintegration: runcimage
80-
docker run -e TESTFLAGS -t --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) --cap-drop=ALL -u rootless $(RUNC_IMAGE) make localintegration
80+
docker run -e TESTFLAGS -t --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localrootlessintegration
8181

82-
# FIXME: This should not be separate from rootlessintegration's method of running.
8382
localrootlessintegration: all
84-
sudo -u rootless -H PATH="${PATH}" bats -t tests/integration${TESTFLAGS}
83+
tests/rootless.sh
8584

8685
shell: all
8786
docker run -e TESTFLAGS -ti --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) bash

libcontainer/configs/validate/rootless.go

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,37 +36,27 @@ func (v *ConfigValidator) rootless(config *configs.Config) error {
3636
return nil
3737
}
3838

39-
func rootlessMappings(config *configs.Config) error {
40-
rootuid, err := config.HostRootUID()
41-
if err != nil {
42-
return fmt.Errorf("failed to get root uid from uidMappings: %v", err)
39+
func hasIDMapping(id int, mappings []configs.IDMap) bool {
40+
for _, m := range mappings {
41+
if id >= m.ContainerID && id < m.ContainerID+m.Size {
42+
return true
43+
}
4344
}
45+
return false
46+
}
47+
48+
func rootlessMappings(config *configs.Config) error {
4449
if euid := geteuid(); euid != 0 {
4550
if !config.Namespaces.Contains(configs.NEWUSER) {
4651
return fmt.Errorf("rootless containers require user namespaces")
4752
}
48-
if rootuid != euid {
49-
return fmt.Errorf("rootless containers cannot map container root to a different host user")
50-
}
5153
}
5254

53-
rootgid, err := config.HostRootGID()
54-
if err != nil {
55-
return fmt.Errorf("failed to get root gid from gidMappings: %v", err)
55+
if len(config.UidMappings) == 0 {
56+
return fmt.Errorf("rootless containers requires at least one UID mapping")
5657
}
57-
58-
// Similar to the above test, we need to make sure that we aren't trying to
59-
// map to a group ID that we don't have the right to be.
60-
if rootgid != getegid() {
61-
return fmt.Errorf("rootless containers cannot map container root to a different host group")
62-
}
63-
64-
// We can only map one user and group inside a container (our own).
65-
if len(config.UidMappings) != 1 || config.UidMappings[0].Size != 1 {
66-
return fmt.Errorf("rootless containers cannot map more than one user")
67-
}
68-
if len(config.GidMappings) != 1 || config.GidMappings[0].Size != 1 {
69-
return fmt.Errorf("rootless containers cannot map more than one group")
58+
if len(config.GidMappings) == 0 {
59+
return fmt.Errorf("rootless containers requires at least one UID mapping")
7060
}
7161

7262
return nil
@@ -104,11 +94,28 @@ func rootlessMount(config *configs.Config) error {
10494
// Check that the options list doesn't contain any uid= or gid= entries
10595
// that don't resolve to root.
10696
for _, opt := range strings.Split(mount.Data, ",") {
107-
if strings.HasPrefix(opt, "uid=") && opt != "uid=0" {
108-
return fmt.Errorf("cannot specify uid= mount options in rootless containers where argument isn't 0")
97+
if strings.HasPrefix(opt, "uid=") {
98+
var uid int
99+
n, err := fmt.Sscanf(opt, "uid=%d", &uid)
100+
if n != 1 || err != nil {
101+
// Ignore unknown mount options.
102+
continue
103+
}
104+
if !hasIDMapping(uid, config.UidMappings) {
105+
return fmt.Errorf("cannot specify uid= mount options for unmapped uid in rootless containers")
106+
}
109107
}
110-
if strings.HasPrefix(opt, "gid=") && opt != "gid=0" {
111-
return fmt.Errorf("cannot specify gid= mount options in rootless containers where argument isn't 0")
108+
109+
if strings.HasPrefix(opt, "gid=") {
110+
var gid int
111+
n, err := fmt.Sscanf(opt, "gid=%d", &gid)
112+
if n != 1 || err != nil {
113+
// Ignore unknown mount options.
114+
continue
115+
}
116+
if !hasIDMapping(gid, config.GidMappings) {
117+
return fmt.Errorf("cannot specify gid= mount options for unmapped gid in rootless containers")
118+
}
112119
}
113120
}
114121
}

libcontainer/configs/validate/rootless_test.go

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -66,28 +66,6 @@ func TestValidateRootlessMappingUid(t *testing.T) {
6666
if err := validator.Validate(config); err == nil {
6767
t.Errorf("Expected error to occur if no uid mappings provided")
6868
}
69-
70-
config = rootlessConfig()
71-
config.UidMappings[0].HostID = geteuid() + 1
72-
if err := validator.Validate(config); err == nil {
73-
t.Errorf("Expected error to occur if geteuid() != mapped uid")
74-
}
75-
76-
config = rootlessConfig()
77-
config.UidMappings[0].Size = 1024
78-
if err := validator.Validate(config); err == nil {
79-
t.Errorf("Expected error to occur if more than one uid mapped")
80-
}
81-
82-
config = rootlessConfig()
83-
config.UidMappings = append(config.UidMappings, configs.IDMap{
84-
HostID: geteuid() + 1,
85-
ContainerID: 0,
86-
Size: 1,
87-
})
88-
if err := validator.Validate(config); err == nil {
89-
t.Errorf("Expected error to occur if more than one uid extent mapped")
90-
}
9169
}
9270

9371
func TestValidateRootlessMappingGid(t *testing.T) {
@@ -98,28 +76,6 @@ func TestValidateRootlessMappingGid(t *testing.T) {
9876
if err := validator.Validate(config); err == nil {
9977
t.Errorf("Expected error to occur if no gid mappings provided")
10078
}
101-
102-
config = rootlessConfig()
103-
config.GidMappings[0].HostID = getegid() + 1
104-
if err := validator.Validate(config); err == nil {
105-
t.Errorf("Expected error to occur if getegid() != mapped gid")
106-
}
107-
108-
config = rootlessConfig()
109-
config.GidMappings[0].Size = 1024
110-
if err := validator.Validate(config); err == nil {
111-
t.Errorf("Expected error to occur if more than one gid mapped")
112-
}
113-
114-
config = rootlessConfig()
115-
config.GidMappings = append(config.GidMappings, configs.IDMap{
116-
HostID: getegid() + 1,
117-
ContainerID: 0,
118-
Size: 1,
119-
})
120-
if err := validator.Validate(config); err == nil {
121-
t.Errorf("Expected error to occur if more than one gid extent mapped")
122-
}
12379
}
12480

12581
/* rootlessMount() */
@@ -149,6 +105,18 @@ func TestValidateRootlessMountUid(t *testing.T) {
149105
if err := validator.Validate(config); err != nil {
150106
t.Errorf("Expected error to not occur when setting uid=0 in mount options: %+v", err)
151107
}
108+
109+
config.Mounts[0].Data = "uid=2"
110+
config.UidMappings[0].Size = 10
111+
if err := validator.Validate(config); err != nil {
112+
t.Errorf("Expected error to not occur when setting uid=2 in mount options and UidMapping[0].size is 10")
113+
}
114+
115+
config.Mounts[0].Data = "uid=20"
116+
config.UidMappings[0].Size = 10
117+
if err := validator.Validate(config); err == nil {
118+
t.Errorf("Expected error to occur when setting uid=20 in mount options and UidMapping[0].size is 10")
119+
}
152120
}
153121

154122
func TestValidateRootlessMountGid(t *testing.T) {
@@ -176,6 +144,18 @@ func TestValidateRootlessMountGid(t *testing.T) {
176144
if err := validator.Validate(config); err != nil {
177145
t.Errorf("Expected error to not occur when setting gid=0 in mount options: %+v", err)
178146
}
147+
148+
config.Mounts[0].Data = "gid=5"
149+
config.GidMappings[0].Size = 10
150+
if err := validator.Validate(config); err != nil {
151+
t.Errorf("Expected error to not occur when setting gid=5 in mount options and GidMapping[0].size is 10")
152+
}
153+
154+
config.Mounts[0].Data = "gid=11"
155+
config.GidMappings[0].Size = 10
156+
if err := validator.Validate(config); err == nil {
157+
t.Errorf("Expected error to occur when setting gid=11 in mount options and GidMapping[0].size is 10")
158+
}
179159
}
180160

181161
/* rootlessCgroup() */

libcontainer/container_linux.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ type linuxContainer struct {
4444
initProcess parentProcess
4545
initProcessStartTime uint64
4646
criuPath string
47+
newuidmapPath string
48+
newgidmapPath string
4749
m sync.Mutex
4850
criuVersion int
4951
state containerState
@@ -1707,6 +1709,12 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
17071709
if !joinExistingUser {
17081710
// write uid mappings
17091711
if len(c.config.UidMappings) > 0 {
1712+
if c.config.Rootless && c.newuidmapPath != "" {
1713+
r.AddData(&Bytemsg{
1714+
Type: UidmapPathAttr,
1715+
Value: []byte(c.newuidmapPath),
1716+
})
1717+
}
17101718
b, err := encodeIDMapping(c.config.UidMappings)
17111719
if err != nil {
17121720
return nil, err
@@ -1727,6 +1735,12 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
17271735
Type: GidmapAttr,
17281736
Value: b,
17291737
})
1738+
if c.config.Rootless && c.newgidmapPath != "" {
1739+
r.AddData(&Bytemsg{
1740+
Type: GidmapPathAttr,
1741+
Value: []byte(c.newgidmapPath),
1742+
})
1743+
}
17301744
// The following only applies if we are root.
17311745
if !c.config.Rootless {
17321746
// check if we have CAP_SETGID to setgroup properly

libcontainer/factory_linux.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
140140
}
141141
Cgroupfs(l)
142142
for _, opt := range options {
143+
if opt == nil {
144+
continue
145+
}
143146
if err := opt(l); err != nil {
144147
return nil, err
145148
}
@@ -160,6 +163,11 @@ type LinuxFactory struct {
160163
// containers.
161164
CriuPath string
162165

166+
// New{u,g}uidmapPath is the path to the binaries used for mapping with
167+
// rootless containers.
168+
NewuidmapPath string
169+
NewgidmapPath string
170+
163171
// Validator provides validation to container configurations.
164172
Validator validate.Validator
165173

@@ -201,6 +209,8 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err
201209
config: config,
202210
initArgs: l.InitArgs,
203211
criuPath: l.CriuPath,
212+
newuidmapPath: l.NewuidmapPath,
213+
newgidmapPath: l.NewgidmapPath,
204214
cgroupManager: l.NewCgroupsManager(config.Cgroups, nil),
205215
}
206216
c.intelRdtManager = nil
@@ -236,6 +246,8 @@ func (l *LinuxFactory) Load(id string) (Container, error) {
236246
config: &state.Config,
237247
initArgs: l.InitArgs,
238248
criuPath: l.CriuPath,
249+
newuidmapPath: l.NewuidmapPath,
250+
newgidmapPath: l.NewgidmapPath,
239251
cgroupManager: l.NewCgroupsManager(state.Config.Cgroups, state.CgroupPaths),
240252
root: containerRoot,
241253
created: state.Created,
@@ -349,3 +361,21 @@ func (l *LinuxFactory) validateID(id string) error {
349361

350362
return nil
351363
}
364+
365+
// NewuidmapPath returns an option func to configure a LinuxFactory with the
366+
// provided ..
367+
func NewuidmapPath(newuidmapPath string) func(*LinuxFactory) error {
368+
return func(l *LinuxFactory) error {
369+
l.NewuidmapPath = newuidmapPath
370+
return nil
371+
}
372+
}
373+
374+
// NewgidmapPath returns an option func to configure a LinuxFactory with the
375+
// provided ..
376+
func NewgidmapPath(newgidmapPath string) func(*LinuxFactory) error {
377+
return func(l *LinuxFactory) error {
378+
l.NewgidmapPath = newgidmapPath
379+
return nil
380+
}
381+
}

libcontainer/init_linux.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -261,25 +261,27 @@ func setupUser(config *initConfig) error {
261261
}
262262
}
263263

264-
if config.Rootless {
265-
if execUser.Uid != 0 {
266-
return fmt.Errorf("cannot run as a non-root user in a rootless container")
267-
}
268-
269-
if execUser.Gid != 0 {
270-
return fmt.Errorf("cannot run as a non-root group in a rootless container")
271-
}
264+
// Rather than just erroring out later in setuid(2) and setgid(2), check
265+
// that the user is mapped here.
266+
if _, err := config.Config.HostUID(int(execUser.Uid)); err != nil {
267+
return fmt.Errorf("cannot set uid to unmapped user in user namespace")
268+
}
269+
if _, err := config.Config.HostGID(int(execUser.Gid)); err != nil {
270+
return fmt.Errorf("cannot set gid to unmapped user in user namespace")
271+
}
272272

273-
// We cannot set any additional groups in a rootless container and thus we
274-
// bail if the user asked us to do so. TODO: We currently can't do this
275-
// earlier, but if libcontainer.Process.User was typesafe this might work.
273+
if config.Rootless {
274+
// We cannot set any additional groups in a rootless container and thus
275+
// we bail if the user asked us to do so. TODO: We currently can't do
276+
// this check earlier, but if libcontainer.Process.User was typesafe
277+
// this might work.
276278
if len(addGroups) > 0 {
277279
return fmt.Errorf("cannot set any additional groups in a rootless container")
278280
}
279281
}
280282

281-
// before we change to the container's user make sure that the processes STDIO
282-
// is correctly owned by the user that we are switching to.
283+
// Before we change to the container's user make sure that the processes
284+
// STDIO is correctly owned by the user that we are switching to.
283285
if err := fixStdioPermissions(config, execUser); err != nil {
284286
return err
285287
}
@@ -298,7 +300,6 @@ func setupUser(config *initConfig) error {
298300
if err := system.Setgid(execUser.Gid); err != nil {
299301
return err
300302
}
301-
302303
if err := system.Setuid(execUser.Uid); err != nil {
303304
return err
304305
}

libcontainer/message_linux.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const (
1818
SetgroupAttr uint16 = 27285
1919
OomScoreAdjAttr uint16 = 27286
2020
RootlessAttr uint16 = 27287
21+
UidmapPathAttr uint16 = 27288
22+
GidmapPathAttr uint16 = 27289
2123
)
2224

2325
type Int32msg struct {

0 commit comments

Comments
 (0)