Skip to content

Commit ec13dd2

Browse files
committed
Merge pull request #12797 from insanitybit:overlay-userxattr
PiperOrigin-RevId: 900848899
2 parents 5d3b128 + 58c7112 commit ec13dd2

4 files changed

Lines changed: 112 additions & 25 deletions

File tree

pkg/sentry/fsimpl/overlay/copy_up.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ func (d *dentry) copyXattrsLocked(ctx context.Context) error {
388388

389389
for _, name := range lowerXattrs {
390390
// Do not copy up overlay attributes.
391-
if isOverlayXattr(name) {
391+
if d.fs.isOverlayXattr(name) {
392392
continue
393393
}
394394

pkg/sentry/fsimpl/overlay/filesystem.go

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,6 @@ import (
3131
"gvisor.dev/gvisor/pkg/sync"
3232
)
3333

34-
// _OVL_XATTR_PREFIX is an extended attribute key prefix to identify overlayfs
35-
// attributes.
36-
// Linux: fs/overlayfs/overlayfs.h:OVL_XATTR_PREFIX
37-
const _OVL_XATTR_PREFIX = linux.XATTR_TRUSTED_PREFIX + "overlay."
38-
39-
// _OVL_XATTR_OPAQUE is an extended attribute key whose value is set to "y" for
40-
// opaque directories.
41-
// Linux: fs/overlayfs/overlayfs.h:OVL_XATTR_OPAQUE
42-
const _OVL_XATTR_OPAQUE = _OVL_XATTR_PREFIX + "opaque"
43-
4434
func isWhiteout(stat *linux.Statx) bool {
4535
return stat.Mode&linux.S_IFMT == linux.S_IFCHR && stat.RdevMajor == 0 && stat.RdevMinor == 0
4636
}
@@ -310,7 +300,7 @@ func (fs *filesystem) lookupLocked(ctx context.Context, parent *dentry, name str
310300
Root: childVD,
311301
Start: childVD,
312302
}, &vfs.GetXattrOptions{
313-
Name: _OVL_XATTR_OPAQUE,
303+
Name: fs.xattrOpaque,
314304
Size: 1,
315305
})
316306
return !(err == nil && opaqueVal == "y")
@@ -745,7 +735,7 @@ func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
745735
// the new directory should not be merged with, so mark as opaque.
746736
// See fs/overlayfs/dir.c:ovl_create_over_whiteout() -> ovl_set_opaque().
747737
if err := vfsObj.SetXattrAt(ctx, fs.creds, &pop, &vfs.SetXattrOptions{
748-
Name: _OVL_XATTR_OPAQUE,
738+
Name: fs.xattrOpaque,
749739
Value: "y",
750740
}); err != nil {
751741
if cleanupErr := vfsObj.RmdirAt(ctx, fs.creds, &pop); cleanupErr != nil {
@@ -763,7 +753,7 @@ func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
763753
// fs.lookupLocked(). Allow it to fail since this is an optimization.
764754
// See fs/overlayfs/dir.c:ovl_create_upper() -> ovl_set_opaque().
765755
_ = vfsObj.SetXattrAt(ctx, fs.creds, &pop, &vfs.SetXattrOptions{
766-
Name: _OVL_XATTR_OPAQUE,
756+
Name: fs.xattrOpaque,
767757
Value: "y",
768758
})
769759
}
@@ -1322,7 +1312,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
13221312
}
13231313
if renamed.isDir() {
13241314
if err := vfsObj.SetXattrAt(ctx, fs.creds, &newpop, &vfs.SetXattrOptions{
1325-
Name: _OVL_XATTR_OPAQUE,
1315+
Name: fs.xattrOpaque,
13261316
Value: "y",
13271317
}); err != nil {
13281318
panic(fmt.Sprintf("unrecoverable overlayfs inconsistency: failed to make renamed directory opaque: %v", err))
@@ -1689,8 +1679,8 @@ func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error
16891679

16901680
// isOverlayXattr returns whether the given extended attribute configures the
16911681
// overlay.
1692-
func isOverlayXattr(name string) bool {
1693-
return strings.HasPrefix(name, _OVL_XATTR_PREFIX)
1682+
func (fs *filesystem) isOverlayXattr(name string) bool {
1683+
return strings.HasPrefix(name, fs.xattrPrefix)
16941684
}
16951685

16961686
// ListXattrAt implements vfs.FilesystemImpl.ListXattrAt.
@@ -1717,7 +1707,7 @@ func (fs *filesystem) listXattr(ctx context.Context, d *dentry, size uint64) ([]
17171707
// Filter out all overlay attributes.
17181708
n := 0
17191709
for _, name := range names {
1720-
if !isOverlayXattr(name) {
1710+
if !fs.isOverlayXattr(name) {
17211711
names[n] = name
17221712
n++
17231713
}
@@ -1745,7 +1735,7 @@ func (fs *filesystem) getXattr(ctx context.Context, d *dentry, creds *auth.Crede
17451735

17461736
// Return EOPNOTSUPP when fetching an overlay attribute.
17471737
// See fs/overlayfs/super.c:ovl_own_xattr_get().
1748-
if isOverlayXattr(opts.Name) {
1738+
if fs.isOverlayXattr(opts.Name) {
17491739
return "", linuxerr.EOPNOTSUPP
17501740
}
17511741

@@ -1783,7 +1773,7 @@ func (fs *filesystem) setXattrLocked(ctx context.Context, d *dentry, mnt *vfs.Mo
17831773

17841774
// Return EOPNOTSUPP when setting an overlay attribute.
17851775
// See fs/overlayfs/super.c:ovl_own_xattr_set().
1786-
if isOverlayXattr(opts.Name) {
1776+
if fs.isOverlayXattr(opts.Name) {
17871777
return linuxerr.EOPNOTSUPP
17881778
}
17891779

@@ -1828,7 +1818,7 @@ func (fs *filesystem) removeXattrLocked(ctx context.Context, d *dentry, mnt *vfs
18281818
// Like SetXattrAt, return EOPNOTSUPP when removing an overlay attribute.
18291819
// Linux passes the remove request to xattr_handler->set.
18301820
// See fs/xattr.c:vfs_removexattr().
1831-
if isOverlayXattr(name) {
1821+
if fs.isOverlayXattr(name) {
18321822
return linuxerr.EOPNOTSUPP
18331823
}
18341824

pkg/sentry/fsimpl/overlay/overlay.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ type filesystem struct {
100100
// used for accesses to the filesystem's layers. creds is immutable.
101101
creds *auth.Credentials
102102

103+
// xattrPrefix is the xattr key prefix for overlay metadata
104+
// ("trusted.overlay." or "user.overlay."). xattrPrefix is immutable.
105+
xattrPrefix string
106+
107+
// xattrOpaque is xattrPrefix + "opaque". xattrOpaque is immutable.
108+
xattrOpaque string
109+
103110
// createCreds is a cache of credentials that is used for create operations.
104111
createCredsMu createCredsMutex `state:"nosave"`
105112
createCreds map[createCredsKey]*auth.Credentials
@@ -173,6 +180,13 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
173180
defer vfsroot.DecRef(ctx)
174181
}
175182

183+
userXattrVal, userXattr := mopts["userxattr"]
184+
if userXattr && userXattrVal != "" {
185+
ctx.Infof("overlay.FilesystemType.GetFilesystem: userxattr option does not take a value but got %q", userXattrVal)
186+
return nil, nil, linuxerr.EINVAL
187+
}
188+
delete(mopts, "userxattr")
189+
176190
if upperPathname, ok := mopts["upperdir"]; ok {
177191
if fsopts.UpperRoot.Ok() {
178192
ctx.Infof("overlay.FilesystemType.GetFilesystem: both upperdir and FilesystemOptions.UpperRoot are specified")
@@ -221,10 +235,10 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
221235
ctx.Infof("overlay.FilesystemType.GetFilesystem: failed to resolve upperdir %q: %v", upperPathname, err)
222236
return nil, nil, err
223237
}
224-
// TODO(b/286942303): Only tmpfs supports whiteouts and
225-
// trusted.overlay attributes. Don't allow to use non-tmpfs
226-
// mounts on upper levels for mounts created through the mount
227-
// syscall. In gVisor configs, users can specify any
238+
// TODO(b/286942303): Only tmpfs supports whiteouts and overlay
239+
// xattrs (trusted.overlay.* or user.overlay.*). Don't allow
240+
// non-tmpfs mounts on upper levels for mounts created through
241+
// the mount syscall. In gVisor configs, users can specify any
228242
// configurations on their own risk.
229243
if !opts.InternalMount && upperRoot.Mount().Filesystem().FilesystemType().Name() != "tmpfs" {
230244
return nil, nil, linuxerr.EINVAL
@@ -308,9 +322,19 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
308322
lowerRoot.IncRef()
309323
}
310324

325+
// Use "user.overlay.*" xattrs instead of "trusted.overlay.*" when
326+
// userxattr is explicitly set, matching Linux behavior.
327+
// See fs/overlayfs/params.c:ovl_parse_param().
328+
xattrPrefix := linux.XATTR_TRUSTED_PREFIX + "overlay."
329+
if userXattr {
330+
xattrPrefix = linux.XATTR_USER_PREFIX + "overlay."
331+
}
332+
311333
fs := &filesystem{
312334
opts: fsopts,
313335
creds: creds.Fork(),
336+
xattrPrefix: xattrPrefix,
337+
xattrOpaque: xattrPrefix + "opaque",
314338
dirDevMinor: dirDevMinor,
315339
lowerDevMinors: make(map[layerDevNumber]uint32),
316340
dirInoCache: make(map[layerDevNoAndIno]uint64),

test/syscalls/linux/mount.cc

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2583,6 +2583,79 @@ TEST(MountTest, OverlayfsSgidBitIsCopiedUp) {
25832583
EXPECT_TRUE(merged_st_after_touch.st_mode & S_ISGID);
25842584
}
25852585
}
2586+
2587+
// Renaming a directory on an overlay inside a user namespace requires
2588+
// user.overlay.* xattrs to mark the directory opaque.
2589+
TEST(MountTest, OverlayfsDirectoryRenameInUserNamespace) {
2590+
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
2591+
auto base_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
2592+
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(base_dir.path())));
2593+
2594+
const std::function<void()> parent = [] {};
2595+
const std::function<void()> child = [&base_dir] {
2596+
TEST_CHECK_SUCCESS(mount("tmpfs", base_dir.path().c_str(), "tmpfs", 0,
2597+
"mode=1777,size=10m"));
2598+
auto tmpfs_cleanup = Cleanup([&base_dir] {
2599+
TEST_CHECK_SUCCESS(umount2(base_dir.path().c_str(), 0));
2600+
});
2601+
2602+
auto lower = JoinPath(base_dir.path(), "lower");
2603+
auto upper = JoinPath(base_dir.path(), "upper");
2604+
auto work = JoinPath(base_dir.path(), "work");
2605+
auto merged = JoinPath(base_dir.path(), "merged");
2606+
TEST_CHECK_SUCCESS(mkdir(lower.c_str(), 0755));
2607+
TEST_CHECK_SUCCESS(mkdir(upper.c_str(), 0755));
2608+
TEST_CHECK_SUCCESS(mkdir(work.c_str(), 0755));
2609+
TEST_CHECK_SUCCESS(mkdir(merged.c_str(), 0755));
2610+
2611+
// 1. Create the DESTINATION directory in lower.
2612+
// (It must be empty so standard POSIX rename allows overwriting it).
2613+
{
2614+
auto lower_renamed = JoinPath(lower, "renamed");
2615+
TEST_CHECK_SUCCESS(mkdir(lower_renamed.c_str(), 0755));
2616+
}
2617+
2618+
std::string opts = "lowerdir=" + lower + ",upperdir=" + upper +
2619+
",workdir=" + work + ",userxattr";
2620+
TEST_CHECK_SUCCESS(
2621+
mount("overlay", merged.c_str(), "overlay", 0, opts.c_str()));
2622+
auto overlayfs_cleanup =
2623+
Cleanup([&merged] { TEST_CHECK_SUCCESS(umount2(merged.c_str(), 0)); });
2624+
2625+
// 2. Create the SOURCE directory directly in merged.
2626+
// Because it doesn't exist in lower, it is a "pure upper" directory.
2627+
auto mydir = JoinPath(merged, "mydir");
2628+
TEST_CHECK_SUCCESS(mkdir(mydir.c_str(), 0755));
2629+
2630+
int fd =
2631+
open(JoinPath(mydir, "file.txt").c_str(), O_WRONLY | O_CREAT, 0644);
2632+
TEST_CHECK(fd >= 0);
2633+
TEST_CHECK_SUCCESS(close(fd));
2634+
2635+
// 3. Rename the pure upper directory over the existing lower directory.
2636+
// - This avoids EXDEV on Linux because 'mydir' is pure upper.
2637+
// - This forces the kernel to apply user.overlay.opaque="y" to 'renamed'
2638+
// so that 'lower/renamed' is hidden.
2639+
auto renamed = JoinPath(merged, "renamed");
2640+
TEST_CHECK_SUCCESS(rename(mydir.c_str(), renamed.c_str()));
2641+
2642+
// 4. Verify the rename succeeded and contents are correct
2643+
struct stat st;
2644+
TEST_CHECK_SUCCESS(stat(renamed.c_str(), &st));
2645+
TEST_CHECK(S_ISDIR(st.st_mode));
2646+
2647+
// Original directory should be gone
2648+
TEST_CHECK(stat(mydir.c_str(), &st) == -1 && errno == ENOENT);
2649+
2650+
// File inside should have moved with the directory
2651+
TEST_CHECK_SUCCESS(stat(JoinPath(renamed, "file.txt").c_str(), &st));
2652+
TEST_CHECK(S_ISREG(st.st_mode));
2653+
};
2654+
2655+
EXPECT_THAT(InForkedUserMountNamespace(parent, child),
2656+
IsPosixErrorOkAndHolds(0));
2657+
}
2658+
25862659
} // namespace
25872660

25882661
} // namespace testing

0 commit comments

Comments
 (0)