From 3db0591c5338deea29ff383a6f79d7f372f01d94 Mon Sep 17 00:00:00 2001 From: Ramkumar Chinchani Date: Fri, 22 Mar 2024 21:47:54 +0000 Subject: [PATCH] fix: handle overlay xattr opaque bit Current behavior determines if a path is a whiteout if a overlay char dev is present. Additionally, also check the extended attrs. For files, this is not important: if a file /foo/bar exists in some two layers, then the higher layer's version will replace the lower layer's version whether or not the opaque bit is set. However, directories behave differently. If /foo exists in two layers and no opaque bit is set, then we see the union of their contents. But if the opaque bit is set on the higher layer's version, then we should not see the lower layer's version. So, in that case, create a whiteout /.wh_foo. Signed-off-by: Ramkumar Chinchani --- oci/layer/generate.go | 4 ++-- oci/layer/tar_extract_linux_test.go | 2 +- oci/layer/utils.go | 26 +++++++++++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/oci/layer/generate.go b/oci/layer/generate.go index a231d2ea4..ffd562473 100644 --- a/oci/layer/generate.go +++ b/oci/layer/generate.go @@ -88,7 +88,7 @@ func GenerateLayer(path string, deltas []mtree.InodeDelta, opt *RepackOptions) ( return errors.Wrapf(err, "couldn't determine overlay whiteout for %s", fullPath) } - whiteout, err := isOverlayWhiteout(fi) + whiteout, err := isOverlayWhiteout(fi, fullPath, tg.fsEval) if err != nil { return err } @@ -166,7 +166,7 @@ func GenerateInsertLayer(root string, target string, opaque bool, opt *RepackOpt } pathInTar := path.Join(target, curPath[len(root):]) - whiteout, err := isOverlayWhiteout(info) + whiteout, err := isOverlayWhiteout(info, curPath, tg.fsEval) if err != nil { return err } diff --git a/oci/layer/tar_extract_linux_test.go b/oci/layer/tar_extract_linux_test.go index 7ab746624..9aa1267ef 100644 --- a/oci/layer/tar_extract_linux_test.go +++ b/oci/layer/tar_extract_linux_test.go @@ -96,7 +96,7 @@ func TestUnpackEntryOverlayFSWhiteout(t *testing.T) { t.Fatalf("failed to stat `file`: %v", err) } - whiteout, err := isOverlayWhiteout(fi) + whiteout, err := isOverlayWhiteout(fi, filepath.Join(dir, "file"), te.fsEval) if err != nil { t.Fatalf("failed to check overlay whiteout: %v", err) } diff --git a/oci/layer/utils.go b/oci/layer/utils.go index f22122f78..8d8e1574f 100644 --- a/oci/layer/utils.go +++ b/oci/layer/utils.go @@ -25,6 +25,7 @@ import ( "github.com/apex/log" rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/umoci/pkg/fseval" "github.com/opencontainers/umoci/pkg/idtools" "github.com/pkg/errors" rootlesscontainers "github.com/rootless-containers/proto/go-proto" @@ -230,7 +231,7 @@ func InnerErrno(err error) error { // isOverlayWhiteout returns true if the FileInfo represents an overlayfs style // whiteout (i.e. mknod c 0 0) and false otherwise. -func isOverlayWhiteout(info os.FileInfo) (bool, error) { +func isOverlayWhiteout(info os.FileInfo, fullPath string, fsEval fseval.FsEval) (bool, error) { var major, minor uint32 switch stat := info.Sys().(type) { case *unix.Stat_t: @@ -243,6 +244,25 @@ func isOverlayWhiteout(info os.FileInfo) (bool, error) { return false, errors.Errorf("[internal error] unknown stat info type %T", info.Sys()) } - return major == 0 && minor == 0 && - info.Mode()&os.ModeCharDevice != 0, nil + if major == 0 && minor == 0 && + info.Mode()&os.ModeCharDevice != 0 { + return true, nil + } + + // also evaluate xattrs which may have opaque value set + attr, err := fsEval.Lgetxattr(fullPath, "user.overlay.opaque") + if err != nil { + v := errors.Cause(err) + if !errors.Is(err, os.ErrNotExist) && v != unix.EOPNOTSUPP && v != unix.ENODATA && v != unix.EPERM && v != unix.EACCES { + return false, errors.Errorf("[internal error] unknown stat info type %T", info.Sys()) + } + + return false, nil + } + + if string(attr) == "y" { + return true, nil + } + + return false, nil }