Skip to content

Commit 64bd448

Browse files
authored
Merge pull request #41964 from thaJeztah/CVE-2021-21284_master
[master] Fix Access to remapped root allows privilege escalation to real root (CVE-2021-21284)
2 parents 3e0025e + 7f5e39b commit 64bd448

File tree

14 files changed

+69
-71
lines changed

14 files changed

+69
-71
lines changed

Diff for: daemon/container_operations_unix.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -466,5 +466,5 @@ func (daemon *Daemon) setupContainerMountsRoot(c *container.Container) error {
466466
if err != nil {
467467
return err
468468
}
469-
return idtools.MkdirAllAndChown(p, 0700, daemon.idMapping.RootPair())
469+
return idtools.MkdirAllAndChown(p, 0701, idtools.CurrentIdentity())
470470
}

Diff for: daemon/create.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,10 @@ func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr
194194
}
195195
ctr.RWLayer = rwLayer
196196

197-
rootIDs := daemon.idMapping.RootPair()
198-
199-
if err := idtools.MkdirAndChown(ctr.Root, 0700, rootIDs); err != nil {
197+
if err := idtools.MkdirAndChown(ctr.Root, 0701, idtools.CurrentIdentity()); err != nil {
200198
return nil, err
201199
}
202-
if err := idtools.MkdirAndChown(ctr.CheckpointDir(), 0700, rootIDs); err != nil {
200+
if err := idtools.MkdirAndChown(ctr.CheckpointDir(), 0700, idtools.CurrentIdentity()); err != nil {
203201
return nil, err
204202
}
205203

Diff for: daemon/daemon.go

+4-6
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
795795
}
796796

797797
// set up the tmpDir to use a canonical path
798-
tmp, err := prepareTempDir(config.Root, rootIDs)
798+
tmp, err := prepareTempDir(config.Root)
799799
if err != nil {
800800
return nil, fmt.Errorf("Unable to get the TempDir under %s: %s", config.Root, err)
801801
}
@@ -861,7 +861,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
861861
}
862862

863863
daemonRepo := filepath.Join(config.Root, "containers")
864-
if err := idtools.MkdirAllAndChown(daemonRepo, 0700, rootIDs); err != nil {
864+
if err := idtools.MkdirAllAndChown(daemonRepo, 0701, idtools.CurrentIdentity()); err != nil {
865865
return nil, err
866866
}
867867

@@ -1374,7 +1374,7 @@ func (daemon *Daemon) Subnets() ([]net.IPNet, []net.IPNet) {
13741374
// prepareTempDir prepares and returns the default directory to use
13751375
// for temporary files.
13761376
// If it doesn't exist, it is created. If it exists, its content is removed.
1377-
func prepareTempDir(rootDir string, rootIdentity idtools.Identity) (string, error) {
1377+
func prepareTempDir(rootDir string) (string, error) {
13781378
var tmpDir string
13791379
if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" {
13801380
tmpDir = filepath.Join(rootDir, "tmp")
@@ -1392,9 +1392,7 @@ func prepareTempDir(rootDir string, rootIdentity idtools.Identity) (string, erro
13921392
}
13931393
}
13941394
}
1395-
// We don't remove the content of tmpdir if it's not the default,
1396-
// it may hold things that do not belong to us.
1397-
return tmpDir, idtools.MkdirAllAndChown(tmpDir, 0700, rootIdentity)
1395+
return tmpDir, idtools.MkdirAllAndChown(tmpDir, 0700, idtools.CurrentIdentity())
13981396
}
13991397

14001398
func (daemon *Daemon) setGenericResources(conf *config.Config) error {

Diff for: daemon/daemon_unix.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -1196,7 +1196,7 @@ func setupRemappedRoot(config *config.Config) (*idtools.IdentityMapping, error)
11961196
return &idtools.IdentityMapping{}, nil
11971197
}
11981198

1199-
func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error {
1199+
func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools.Identity) error {
12001200
config.Root = rootDir
12011201
// the docker root metadata directory needs to have execute permissions for all users (g+x,o+x)
12021202
// so that syscalls executing as non-root, operating on subdirectories of the graph root
@@ -1221,10 +1221,16 @@ func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools
12211221
// a new subdirectory with ownership set to the remapped uid/gid (so as to allow
12221222
// `chdir()` to work for containers namespaced to that uid/gid)
12231223
if config.RemappedRoot != "" {
1224-
config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", rootIdentity.UID, rootIdentity.GID))
1224+
id := idtools.CurrentIdentity()
1225+
// First make sure the current root dir has the correct perms.
1226+
if err := idtools.MkdirAllAndChown(config.Root, 0701, id); err != nil {
1227+
return errors.Wrapf(err, "could not create or set daemon root permissions: %s", config.Root)
1228+
}
1229+
1230+
config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", remappedRoot.UID, remappedRoot.GID))
12251231
logrus.Debugf("Creating user namespaced daemon root: %s", config.Root)
12261232
// Create the root directory if it doesn't exist
1227-
if err := idtools.MkdirAllAndChown(config.Root, 0700, rootIdentity); err != nil {
1233+
if err := idtools.MkdirAllAndChown(config.Root, 0701, id); err != nil {
12281234
return fmt.Errorf("Cannot create daemon root: %s: %v", config.Root, err)
12291235
}
12301236
// we also need to verify that any pre-existing directories in the path to
@@ -1237,7 +1243,7 @@ func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools
12371243
if dirPath == "/" {
12381244
break
12391245
}
1240-
if !idtools.CanAccess(dirPath, rootIdentity) {
1246+
if !idtools.CanAccess(dirPath, remappedRoot) {
12411247
return fmt.Errorf("a subdirectory in your graphroot path (%s) restricts access to the remapped root uid/gid; please fix by allowing 'o+x' permissions on existing directories", config.Root)
12421248
}
12431249
}

Diff for: daemon/graphdriver/aufs/aufs.go

+3-6
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,15 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
129129
locker: locker.New(),
130130
}
131131

132-
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
133-
if err != nil {
134-
return nil, err
135-
}
132+
currentID := idtools.CurrentIdentity()
136133
// Create the root aufs driver dir
137-
if err := idtools.MkdirAllAndChown(root, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
134+
if err := idtools.MkdirAllAndChown(root, 0701, currentID); err != nil {
138135
return nil, err
139136
}
140137

141138
// Populate the dir structure
142139
for _, p := range paths {
143-
if err := idtools.MkdirAllAndChown(path.Join(root, p), 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
140+
if err := idtools.MkdirAllAndChown(path.Join(root, p), 0701, currentID); err != nil {
144141
return nil, err
145142
}
146143
}

Diff for: daemon/graphdriver/btrfs/btrfs.go

+3-7
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
7070
return nil, graphdriver.ErrPrerequisites
7171
}
7272

73-
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
74-
if err != nil {
75-
return nil, err
76-
}
77-
if err := idtools.MkdirAllAndChown(home, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
73+
if err := idtools.MkdirAllAndChown(home, 0701, idtools.CurrentIdentity()); err != nil {
7874
return nil, err
7975
}
8076

@@ -525,7 +521,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
525521
if err != nil {
526522
return err
527523
}
528-
if err := idtools.MkdirAllAndChown(subvolumes, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
524+
if err := idtools.MkdirAllAndChown(subvolumes, 0701, idtools.CurrentIdentity()); err != nil {
529525
return err
530526
}
531527
if parent == "" {
@@ -560,7 +556,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
560556
if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil {
561557
return err
562558
}
563-
if err := idtools.MkdirAllAndChown(quotas, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
559+
if err := idtools.MkdirAllAndChown(quotas, 0700, idtools.CurrentIdentity()); err != nil {
564560
return err
565561
}
566562
if err := ioutil.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(driver.options.size)), 0644); err != nil {

Diff for: daemon/graphdriver/fuse-overlayfs/fuseoverlayfs.go

+5-9
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
8888
return nil, graphdriver.ErrNotSupported
8989
}
9090

91-
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
92-
if err != nil {
93-
return nil, err
94-
}
95-
// Create the driver home dir
96-
if err := idtools.MkdirAllAndChown(path.Join(home, linkDir), 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
91+
if err := idtools.MkdirAllAndChown(path.Join(home, linkDir), 0701, idtools.CurrentIdentity()); err != nil {
9792
return nil, err
9893
}
9994

@@ -178,10 +173,11 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr
178173
}
179174
root := idtools.Identity{UID: rootUID, GID: rootGID}
180175

181-
if err := idtools.MkdirAllAndChown(path.Dir(dir), 0700, root); err != nil {
176+
currentID := idtools.CurrentIdentity()
177+
if err := idtools.MkdirAllAndChown(path.Dir(dir), 0701, currentID); err != nil {
182178
return err
183179
}
184-
if err := idtools.MkdirAndChown(dir, 0700, root); err != nil {
180+
if err := idtools.MkdirAndChown(dir, 0701, currentID); err != nil {
185181
return err
186182
}
187183

@@ -215,7 +211,7 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr
215211
return nil
216212
}
217213

218-
if err := idtools.MkdirAndChown(path.Join(dir, workDirName), 0700, root); err != nil {
214+
if err := idtools.MkdirAndChown(path.Join(dir, workDirName), 0701, currentID); err != nil {
219215
return err
220216
}
221217

Diff for: daemon/graphdriver/overlay/overlay.go

+7-9
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,8 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
156156
logrus.WithField("storage-driver", "overlay").Warn(overlayutils.ErrDTypeNotSupported("overlay", backingFs))
157157
}
158158

159-
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
160-
if err != nil {
161-
return nil, err
162-
}
163159
// Create the driver home dir
164-
if err := idtools.MkdirAllAndChown(home, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
160+
if err := idtools.MkdirAllAndChown(home, 0701, idtools.CurrentIdentity()); err != nil {
165161
return nil, err
166162
}
167163

@@ -265,10 +261,11 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
265261
}
266262
root := idtools.Identity{UID: rootUID, GID: rootGID}
267263

268-
if err := idtools.MkdirAllAndChown(path.Dir(dir), 0700, root); err != nil {
264+
currentID := idtools.CurrentIdentity()
265+
if err := idtools.MkdirAllAndChown(path.Dir(dir), 0701, currentID); err != nil {
269266
return err
270267
}
271-
if err := idtools.MkdirAndChown(dir, 0700, root); err != nil {
268+
if err := idtools.MkdirAndChown(dir, 0701, currentID); err != nil {
272269
return err
273270
}
274271

@@ -281,6 +278,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
281278

282279
// Toplevel images are just a "root" dir
283280
if parent == "" {
281+
// This must be 0755 otherwise unprivileged users will in the container will not be able to read / in the container
284282
return idtools.MkdirAndChown(path.Join(dir, "root"), 0755, root)
285283
}
286284

@@ -301,7 +299,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
301299
if err := idtools.MkdirAndChown(path.Join(dir, "work"), 0700, root); err != nil {
302300
return err
303301
}
304-
return ioutil.WriteFile(path.Join(dir, "lower-id"), []byte(parent), 0666)
302+
return ioutil.WriteFile(path.Join(dir, "lower-id"), []byte(parent), 0600)
305303
}
306304

307305
// Otherwise, copy the upper and the lower-id from the parent
@@ -311,7 +309,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
311309
return err
312310
}
313311

314-
if err := ioutil.WriteFile(path.Join(dir, "lower-id"), lowerID, 0666); err != nil {
312+
if err := ioutil.WriteFile(path.Join(dir, "lower-id"), lowerID, 0600); err != nil {
315313
return err
316314
}
317315

Diff for: daemon/graphdriver/overlay2/overlay.go

+4-8
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
165165
logger.Warn(overlayutils.ErrDTypeNotSupported("overlay2", backingFs))
166166
}
167167

168-
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
169-
if err != nil {
170-
return nil, err
171-
}
172-
// Create the driver home dir
173-
if err := idtools.MkdirAllAndChown(path.Join(home, linkDir), 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
168+
if err := idtools.MkdirAllAndChown(path.Join(home, linkDir), 0701, idtools.CurrentIdentity()); err != nil {
174169
return nil, err
175170
}
176171

@@ -339,11 +334,12 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr
339334
return err
340335
}
341336
root := idtools.Identity{UID: rootUID, GID: rootGID}
337+
current := idtools.CurrentIdentity()
342338

343-
if err := idtools.MkdirAllAndChown(path.Dir(dir), 0700, root); err != nil {
339+
if err := idtools.MkdirAllAndChown(path.Dir(dir), 0701, current); err != nil {
344340
return err
345341
}
346-
if err := idtools.MkdirAndChown(dir, 0700, root); err != nil {
342+
if err := idtools.MkdirAndChown(dir, 0701, current); err != nil {
347343
return err
348344
}
349345

Diff for: daemon/graphdriver/vfs/driver.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
3838
return nil, err
3939
}
4040

41-
rootIDs := d.idMapping.RootPair()
42-
if err := idtools.MkdirAllAndChown(home, 0700, rootIDs); err != nil {
41+
if err := idtools.MkdirAllAndChown(home, 0701, idtools.CurrentIdentity()); err != nil {
4342
return nil, err
4443
}
4544

@@ -141,7 +140,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
141140
func (d *Driver) create(id, parent string, size uint64) error {
142141
dir := d.dir(id)
143142
rootIDs := d.idMapping.RootPair()
144-
if err := idtools.MkdirAllAndChown(filepath.Dir(dir), 0700, rootIDs); err != nil {
143+
if err := idtools.MkdirAllAndChown(filepath.Dir(dir), 0701, idtools.CurrentIdentity()); err != nil {
145144
return err
146145
}
147146
if err := idtools.MkdirAndChown(dir, 0755, rootIDs); err != nil {

Diff for: daemon/graphdriver/zfs/zfs.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,7 @@ func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdri
104104
return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
105105
}
106106

107-
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
108-
if err != nil {
109-
return nil, fmt.Errorf("Failed to get root uid/guid: %v", err)
110-
}
111-
if err := idtools.MkdirAllAndChown(base, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
107+
if err := idtools.MkdirAllAndChown(base, 0701, idtools.CurrentIdentity()); err != nil {
112108
return nil, fmt.Errorf("Failed to create '%s': %v", base, err)
113109
}
114110

Diff for: pkg/idtools/idtools.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ const (
3535

3636
// MkdirAllAndChown creates a directory (include any along the path) and then modifies
3737
// ownership to the requested uid/gid. If the directory already exists, this
38-
// function will still change ownership to the requested uid/gid pair.
38+
// function will still change ownership and permissions.
3939
func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error {
4040
return mkdirAs(path, mode, owner, true, true)
4141
}
4242

4343
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
44-
// If the directory already exists, this function still changes ownership.
44+
// If the directory already exists, this function still changes ownership and permissions.
4545
// Note that unlike os.Mkdir(), this function does not return IsExist error
4646
// in case path already exists.
4747
func MkdirAndChown(path string, mode os.FileMode, owner Identity) error {
@@ -50,7 +50,7 @@ func MkdirAndChown(path string, mode os.FileMode, owner Identity) error {
5050

5151
// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
5252
// ownership ONLY of newly created directories to the requested uid/gid. If the
53-
// directories along the path exist, no change of ownership will be performed
53+
// directories along the path exist, no change of ownership or permissions will be performed
5454
func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error {
5555
return mkdirAs(path, mode, owner, true, false)
5656
}
@@ -234,3 +234,8 @@ func parseSubidFile(path, username string) (ranges, error) {
234234

235235
return rangeList, s.Err()
236236
}
237+
238+
// CurrentIdentity returns the identity of the current process
239+
func CurrentIdentity() Identity {
240+
return Identity{UID: os.Getuid(), GID: os.Getegid()}
241+
}

Diff for: pkg/idtools/idtools_unix.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting
4040
}
4141

4242
// short-circuit--we were called with an existing directory and chown was requested
43-
return lazyChown(path, owner.UID, owner.GID, stat)
43+
return setPermissions(path, mode, owner.UID, owner.GID, stat)
4444
}
4545

4646
if os.IsNotExist(err) {
@@ -71,7 +71,7 @@ func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting
7171
// even if it existed, we will chown the requested path + any subpaths that
7272
// didn't exist when we called MkdirAll
7373
for _, pathComponent := range paths {
74-
if err := lazyChown(pathComponent, owner.UID, owner.GID, nil); err != nil {
74+
if err := setPermissions(pathComponent, mode, owner.UID, owner.GID, nil); err != nil {
7575
return err
7676
}
7777
}
@@ -213,17 +213,23 @@ func callGetent(database, key string) (io.Reader, error) {
213213
return bytes.NewReader(out), nil
214214
}
215215

216-
// lazyChown performs a chown only if the uid/gid don't match what's requested
216+
// setPermissions performs a chown/chmod only if the uid/gid don't match what's requested
217217
// Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the
218218
// dir is on an NFS share, so don't call chown unless we absolutely must.
219-
func lazyChown(p string, uid, gid int, stat *system.StatT) error {
219+
// Likewise for setting permissions.
220+
func setPermissions(p string, mode os.FileMode, uid, gid int, stat *system.StatT) error {
220221
if stat == nil {
221222
var err error
222223
stat, err = system.Stat(p)
223224
if err != nil {
224225
return err
225226
}
226227
}
228+
if os.FileMode(stat.Mode()).Perm() != mode.Perm() {
229+
if err := os.Chmod(p, mode.Perm()); err != nil {
230+
return err
231+
}
232+
}
227233
if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
228234
return nil
229235
}

0 commit comments

Comments
 (0)