Skip to content

Commit 6d14660

Browse files
committed
merge #592 into opencontainers/umoci:main
Aleksa Sarai (1): oci: dir: ensure ownership of new files matches image dir ownership LGTMs: tych0 cyphar
2 parents a652677 + 7a79fc3 commit 6d14660

File tree

3 files changed

+78
-0
lines changed

3 files changed

+78
-0
lines changed

oci/cas/dir/dir.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,15 @@ func blobPath(digest digest.Digest) (string, error) {
7777
return filepath.Join(blobDirectory, algo.String(), hash), nil
7878
}
7979

80+
type uidgid struct {
81+
uid, gid int
82+
}
83+
8084
type dirEngine struct {
8185
path string
8286
temp string
8387
tempFile *os.File
88+
owner *uidgid
8489
}
8590

8691
func (e *dirEngine) ensureTempDir() error {
@@ -107,6 +112,26 @@ func (e *dirEngine) ensureTempDir() error {
107112
return nil
108113
}
109114

115+
// chown changes the ownership of the provided file to match the owner of the
116+
// cas directory itself (in order to avoid a root "umoci repack" creating
117+
// inaccessible files for the original user).
118+
func (e *dirEngine) fchown(file *os.File) error {
119+
if os.Geteuid() != 0 {
120+
return nil // skip chown if not running as root
121+
}
122+
if e.owner == nil {
123+
var stat unix.Stat_t
124+
if err := unix.Stat(e.path, &stat); err != nil {
125+
return fmt.Errorf("stat cas dir: %w", err)
126+
}
127+
e.owner = &uidgid{
128+
uid: int(stat.Uid),
129+
gid: int(stat.Gid),
130+
}
131+
}
132+
return file.Chown(e.owner.uid, e.owner.gid)
133+
}
134+
110135
// verify ensures that the image is valid.
111136
func (e *dirEngine) validate() error {
112137
content, err := os.ReadFile(filepath.Join(e.path, layoutFile))
@@ -177,6 +202,9 @@ func (e *dirEngine) PutBlob(_ context.Context, reader io.Reader) (_ digest.Diges
177202
if err != nil {
178203
return "", -1, fmt.Errorf("copy to temporary blob: %w", err)
179204
}
205+
if err := e.fchown(fh); err != nil {
206+
return "", -1, fmt.Errorf("fix ownership of new blob: %w", err)
207+
}
180208
if err := fh.Sync(); err != nil {
181209
return "", -1, fmt.Errorf("sync temporary blob: %w", err)
182210
}
@@ -268,6 +296,9 @@ func (e *dirEngine) PutIndex(_ context.Context, index ispec.Index) (Err error) {
268296
if err := json.NewEncoder(fh).Encode(index); err != nil {
269297
return fmt.Errorf("write temporary index: %w", err)
270298
}
299+
if err := e.fchown(fh); err != nil {
300+
return fmt.Errorf("fix ownership of new index: %w", err)
301+
}
271302
if err := fh.Sync(); err != nil {
272303
return fmt.Errorf("sync temporary index: %w", err)
273304
}

test/helpers.bash

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ function teardown_tmpdirs() {
8282
IMAGE="$(setup_tmpdir)/image"
8383
TAG="${SOURCE_TAG}"
8484

85+
function fail() {
86+
echo "FAILURE:" "$@" >&2
87+
false
88+
}
89+
8590
# Allows a test to specify what things it requires. If the environment can't
8691
# support it, the test is skipped with a message.
8792
function requires() {
@@ -114,6 +119,17 @@ function image-verify() {
114119
}
115120
echo $f: valid tar archive
116121
done
122+
123+
# Validate that all inodes are owned by the same uid:gid as the root
124+
# directory, which is the correct behaviour for us.
125+
owner="$(stat -c "%u:%g" "$ocidir")"
126+
allowners="$(find "$ocidir" -print0 | xargs -0 stat -c "%u:%g %n")"
127+
if grep -v "^$owner" <<<"$allowners" ; then
128+
echo "$allowners"
129+
fail "image $ocidir contains subpaths with incorrect owners"
130+
fi
131+
132+
# oci-spec validation
117133
oci-image-tool validate --type "imageLayout" "$ocidir"
118134
return $?
119135
}

test/repack.bats

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,3 +1055,34 @@ OCI_MEDIATYPE_LAYER="application/vnd.oci.image.layer.v1.tar"
10551055
[ "$status" -eq 0 ]
10561056
[[ "$output" == *"application/x-zstd"* ]]
10571057
}
1058+
1059+
@test "umoci repack [cas file ownership]" {
1060+
requires root
1061+
1062+
# Change the image ownership to a random uid:gid.
1063+
chown -R 1234:5678 "$IMAGE"
1064+
image-verify "$IMAGE"
1065+
1066+
# Unpack the image.
1067+
new_bundle_rootfs
1068+
umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE"
1069+
[ "$status" -eq 0 ]
1070+
bundle-verify "$BUNDLE"
1071+
1072+
# Make some changes.
1073+
touch "$ROOTFS/etc"
1074+
echo "first file" > "$ROOTFS/newfile"
1075+
mkdir "$ROOTFS/newdir"
1076+
echo "subfile" > "$ROOTFS/newdir/anotherfile"
1077+
ln -s "this is a dummy symlink" "$ROOTFS/newdir/link"
1078+
1079+
umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle "$BUNDLE"
1080+
[ "$status" -eq 0 ]
1081+
image-verify "$IMAGE"
1082+
1083+
# image-verify checks that the ownership is correct, but double-check
1084+
# explicitly that all of the files are owned by the user we expected.
1085+
sane_run bats_pipe find "$IMAGE" -print0 \| xargs -0 stat -c "%u:%g %n" \| grep -v "^1234:5678 "
1086+
[ "$status" -ne 0 ]
1087+
[ -z "$output" ]
1088+
}

0 commit comments

Comments
 (0)