Skip to content

Commit db6342f

Browse files
authored
Merge pull request moby#3671 from sipsma/fix-merge-file-caps
diffapply: do chown before xattrs
2 parents 30eb659 + 0a36f1a commit db6342f

27 files changed

+5411
-19
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ require (
8787
google.golang.org/genproto v0.0.0-20230131230820-1c016267d619
8888
google.golang.org/grpc v1.52.3
8989
google.golang.org/protobuf v1.28.1
90+
kernel.org/pub/linux/libs/security/libcap/cap v1.2.67
9091
)
9192

9293
require (
@@ -156,4 +157,5 @@ require (
156157
golang.org/x/text v0.7.0 // indirect
157158
golang.org/x/tools v0.5.0 // indirect
158159
gopkg.in/yaml.v3 v3.0.1 // indirect
160+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 // indirect
159161
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,6 +1890,10 @@ k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuB
18901890
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
18911891
k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
18921892
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
1893+
kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 h1:sPQ9qlSNR26fToTKbxe/HDWJlXvBLqGmt84LGCQkOy0=
1894+
kernel.org/pub/linux/libs/security/libcap/cap v1.2.67/go.mod h1:GkntoBuwffz19qtdFVB+k2NtWNN+yCKnC/Ykv/hMiTU=
1895+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 h1:NxbXJ7pDVq0FKBsqjieT92QDXI2XaqH2HAi4QcCOHt8=
1896+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.67/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
18931897
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
18941898
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
18951899
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=

snapshot/diffapply_unix.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,18 @@ func (a *applier) applyCopy(ctx context.Context, ca *changeApply) error {
379379
return errors.Errorf("unhandled file type %d during merge at path %q", ca.srcStat.Mode&unix.S_IFMT, ca.srcPath)
380380
}
381381

382+
// NOTE: it's important that chown happens before setting xattrs due to the fact that chown will
383+
// reset the security.capabilities xattr which results in file capabilities being lost.
384+
if err := os.Lchown(ca.dstPath, int(ca.srcStat.Uid), int(ca.srcStat.Gid)); err != nil {
385+
return errors.Wrap(err, "failed to chown during apply")
386+
}
387+
388+
if ca.srcStat.Mode&unix.S_IFMT != unix.S_IFLNK {
389+
if err := unix.Chmod(ca.dstPath, ca.srcStat.Mode); err != nil {
390+
return errors.Wrapf(err, "failed to chmod path %q during apply", ca.dstPath)
391+
}
392+
}
393+
382394
if ca.srcPath != "" {
383395
xattrs, err := sysx.LListxattr(ca.srcPath)
384396
if err != nil {
@@ -410,16 +422,6 @@ func (a *applier) applyCopy(ctx context.Context, ca *changeApply) error {
410422
}
411423
}
412424

413-
if err := os.Lchown(ca.dstPath, int(ca.srcStat.Uid), int(ca.srcStat.Gid)); err != nil {
414-
return errors.Wrap(err, "failed to chown during apply")
415-
}
416-
417-
if ca.srcStat.Mode&unix.S_IFMT != unix.S_IFLNK {
418-
if err := unix.Chmod(ca.dstPath, ca.srcStat.Mode); err != nil {
419-
return errors.Wrapf(err, "failed to chmod path %q during apply", ca.dstPath)
420-
}
421-
}
422-
423425
atimeSpec := unix.Timespec{Sec: ca.srcStat.Atim.Sec, Nsec: ca.srcStat.Atim.Nsec}
424426
mtimeSpec := unix.Timespec{Sec: ca.srcStat.Mtim.Sec, Nsec: ca.srcStat.Mtim.Nsec}
425427
if ca.srcStat.Mode&unix.S_IFMT != unix.S_IFDIR {

snapshot/snapshotter_test.go

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/pkg/errors"
2727
"github.com/stretchr/testify/require"
2828
bolt "go.etcd.io/bbolt"
29+
libcap "kernel.org/pub/linux/libs/security/libcap/cap"
2930
)
3031

3132
func newSnapshotter(ctx context.Context, t *testing.T, snapshotterName string) (_ context.Context, _ *mergeSnapshotter, rerr error) {
@@ -372,6 +373,41 @@ func TestHardlinks(t *testing.T) {
372373
}
373374
}
374375

376+
func TestMergeFileCapabilities(t *testing.T) {
377+
requireRoot(t)
378+
for _, snName := range []string{"overlayfs", "native", "native-nohardlink"} {
379+
snName := snName
380+
t.Run(snName, func(t *testing.T) {
381+
t.Parallel()
382+
383+
ctx, sn, err := newSnapshotter(context.Background(), t, snName)
384+
require.NoError(t, err)
385+
386+
setCaps := "cap_net_bind_service=+ep"
387+
base1Snap := committedKey(ctx, t, sn, identity.NewID(), "",
388+
fstest.CreateFile("hasCaps", []byte("capable"), 0700),
389+
fstest.Chown("hasCaps", 1000, 1000),
390+
setFileCap("hasCaps", setCaps),
391+
)
392+
base2Snap := committedKey(ctx, t, sn, identity.NewID(), "",
393+
fstest.CreateFile("foo", []byte("bar"), 0600),
394+
)
395+
396+
mergeSnap := mergeKey(ctx, t, sn, identity.NewID(), []Diff{
397+
{"", base1Snap.Name},
398+
{"", base2Snap.Name},
399+
})
400+
401+
actualCaps := getFileCap(ctx, t, sn, mergeSnap.Name, "hasCaps")
402+
require.Equal(t, "cap_net_bind_service=ep", actualCaps)
403+
stat := statPath(ctx, t, sn, mergeSnap.Name, "hasCaps")
404+
require.EqualValues(t, 1000, stat.Uid)
405+
require.EqualValues(t, 1000, stat.Gid)
406+
require.EqualValues(t, 0700, stat.Mode&0777)
407+
})
408+
}
409+
}
410+
375411
func TestUsage(t *testing.T) {
376412
for _, snName := range []string{"overlayfs", "native", "native-nohardlink"} {
377413
snName := snName
@@ -559,7 +595,7 @@ func requireMtime(t *testing.T, path string, mtime time.Time) {
559595
require.Equal(t, mtime.UnixNano(), stat.Mtim.Nano())
560596
}
561597

562-
func tryStatPath(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string) (st *syscall.Stat_t) {
598+
func pathCallback[T any](ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string, cb func(t *testing.T, path string) *T) *T {
563599
t.Helper()
564600
mounts, cleanup := getMounts(ctx, t, sn, key)
565601
defer cleanup()
@@ -577,24 +613,32 @@ func tryStatPath(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, p
577613
}
578614
}
579615
if upperdir != "" {
580-
st = trySyscallStat(t, filepath.Join(upperdir, path))
581-
if st != nil {
582-
return st
616+
r := cb(t, filepath.Join(upperdir, path))
617+
if r != nil {
618+
return r
583619
}
584620
}
585621
for _, lowerdir := range lowerdirs {
586-
st = trySyscallStat(t, filepath.Join(lowerdir, path))
587-
if st != nil {
588-
return st
622+
r := cb(t, filepath.Join(lowerdir, path))
623+
if r != nil {
624+
return r
589625
}
590626
}
591627
return nil
592628
}
593629

630+
var r *T
594631
withMount(ctx, t, sn, key, func(root string) {
595-
st = trySyscallStat(t, filepath.Join(root, path))
632+
r = cb(t, filepath.Join(root, path))
633+
})
634+
return r
635+
}
636+
637+
func tryStatPath(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string) *syscall.Stat_t {
638+
t.Helper()
639+
return pathCallback(ctx, t, sn, key, path, func(t *testing.T, path string) *syscall.Stat_t {
640+
return trySyscallStat(t, path)
596641
})
597-
return st
598642
}
599643

600644
func statPath(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string) (st *syscall.Stat_t) {
@@ -610,3 +654,36 @@ func requireRoot(t *testing.T) {
610654
t.Skip("test requires root")
611655
}
612656
}
657+
658+
func setFileCap(path string, caps string) fstest.Applier {
659+
return applyFn(func(root string) error {
660+
path := filepath.Join(root, path)
661+
capSet, err := libcap.FromText(caps)
662+
if err != nil {
663+
return err
664+
}
665+
return capSet.SetFile(path)
666+
})
667+
}
668+
669+
func getFileCap(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string) string {
670+
t.Helper()
671+
caps := pathCallback(ctx, t, sn, key, path, func(t *testing.T, path string) *string {
672+
t.Helper()
673+
capSet, err := libcap.GetFile(path)
674+
if err != nil {
675+
require.ErrorIs(t, err, os.ErrNotExist)
676+
return nil
677+
}
678+
caps := capSet.String()
679+
return &caps
680+
})
681+
require.NotNil(t, caps)
682+
return *caps
683+
}
684+
685+
type applyFn func(root string) error
686+
687+
func (a applyFn) Apply(root string) error {
688+
return a(root)
689+
}

0 commit comments

Comments
 (0)