summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Xu (Hello71) <alex_y_xu@yahoo.ca>2021-06-29 16:59:52 -0400
committerAlex Xu (Hello71) <alex_y_xu@yahoo.ca>2021-06-30 12:20:57 -0400
commitd571da78eaaa8408b4a6be8d8945df93624f41a9 (patch)
tree16c8156bd65477a1317b02688213e9825085647b
parent0c58c357d2b3288c4a4502d5ac2ecd34cc441f4f (diff)
downloadtmpoverlay-d571da78eaaa8408b4a6be8d8945df93624f41a9.tar.xz
tmpoverlay-d571da78eaaa8408b4a6be8d8945df93624f41a9.zip
improve userns support, logging, add minification
-rw-r--r--.gitignore1
-rw-r--r--Makefile14
-rw-r--r--README.rst23
-rwxr-xr-xtmpoverlay.sh (renamed from tmpoverlay)65
4 files changed, 69 insertions, 34 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59b3be3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/tmpoverlay
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..aacc7e9
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+prefix ?= /usr/local
+bindir ?= $(prefix)/bin
+
+tmpoverlay: tmpoverlay.sh
+ sed -e '1p;1t;/^ *#.*/d' $< > $@
+ chmod +x $@
+
+clean:
+ rm -f tmpoverlay
+
+install:
+ install -Dm755 tmpoverlay $(DESTDIR)$(bindir)/tmpoverlay
+
+.PHONY: clean install
diff --git a/README.rst b/README.rst
index ceeaf84..3e8e4c5 100644
--- a/README.rst
+++ b/README.rst
@@ -8,6 +8,8 @@ Features
--------
- minimal requirements (sh, mount, getopt)
+- single shell script, no compilation required
+- small (7 KB, 2 KB after gzip -9)
Benefits over manually calling ``mkdir /tmp/x; mount ...``
@@ -26,10 +28,12 @@ mount point, it continues to access the original directory, not the overlaid
one. Each process also has a cached root directory pointer, which can only be
modified by chroot (internally) or pivot_root (globally). The pivot_root(2)_
and pivot_root(8)_ man pages should be fully read and understood before using
-tmpoverlay to overmount ``/``.
+tmpoverlay to overmount ``/``. It is also highly recommended to read `busybox
+switch_root comment`_.
.. _pivot_root(2): https://man7.org/linux/man-pages/man2/pivot_root.2.html
.. _pivot_root(8): https://man7.org/linux/man-pages/man8/pivot_root.8.html
+.. _busybox switch_root comment: https://git.busybox.net/busybox/tree/util-linux/switch_root.c?id=3b267e99259191eca0865179a56429c4c441e2b2#n289
Changes to underlying filesystems
---------------------------------
@@ -44,6 +48,23 @@ while the overlay is mounted is not supported:
.. _the kernel overlayfs documentation: https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#changes-to-underlying-filesystems
+Unprivileged operation using user namespaces
+--------------------------------------------
+
+Since Linux 5.11, overlayfs can be mounted in unprivileged user namespaces.
+This means that it is possible to temporarily place an overlay in a local
+context. For example, ``unshare -Umc --keep-caps sh -c 'tmpoverlay . && exec
+setpriv --inh-caps=-all $SHELL'`` has a similar effect to ``tmpoverlay .``, but
+does not require privileges. In exchange, it only takes effect within the newly
+started shell, similar to environment variables.
+
+Note that tmpfs overlay doesn't work properly with unmapped UIDs. In other
+words, after running tmpoverlay, only files owned by the current user can be
+modified; modifying other files will have unpredictable results.
+
+This issue can be mitigated starting with Linux 5.12 using idmap, but this is
+not integrated in tmpoverlay due to a lack of standard utilities.
+
Pseudo-filesystems
------------------
diff --git a/tmpoverlay b/tmpoverlay.sh
index e817e87..9ca2d00 100755
--- a/tmpoverlay
+++ b/tmpoverlay.sh
@@ -22,30 +22,23 @@ examples:
tmpoverlay /etc # make read-only /etc writable
tmpoverlay /a /b /c /merged # merge /a, /b, /c, and a fresh tmpfs
tmpoverlay / # USE WITH CAUTION, see docs
+ unshare -Umc --keep-caps sh -c 'tmpoverlay . && exec \$SHELL' # make cwd writable as non-root
EOF
exit "$1"
}
log() {
- logn "$@"
- printf '\n' >&2
-}
-
-logn() {
# not equivalent to printf "tmpoverlay: $@"
printf 'tmpoverlay: ' >&2
printf "$@" >&2
+ printf '\n' >&2
}
logv() {
[ -z "$verbose" ] || log "$@"
}
-logvn() {
- [ -z "$verbose" ] || logn "$@"
-}
-
-cmdv() {
+cmd() {
logv '%s ' "$@"
"$@"
}
@@ -126,17 +119,18 @@ dest=$(canon "$1")
[ -n "$lowerdir" ] || lowerdir=$dest
logv 'creating tmpdir'
-tmpdir=$(umask 077; mktemp -dt tmpoverlay.XXXXXXXXXX) || die
+tmpdir=$(cmd umask 077; cmd mktemp -dt tmpoverlay.XXXXXXXXXX) || die
logv 'created tmpdir: %s' "$tmpdir"
+# won't trigger on overmounting /, which is actually correct
case "$tmpdir" in
"$dest"/*) log "warning: tmpdir cannot be cleaned up after overmounting $dest"
esac
-mount -t tmpfs ${tmpfs_opts:+-o "$tmpfs_opts"} $verbose tmpfs "$tmpdir" || { rmdir "$tmpdir"; die; }
-mkfifo "$tmpdir/fifo" || { umount "$tmpdir"; rmdir "$tmpdir"; die; }
+cmd mount -t tmpfs ${tmpfs_opts:+-o "$tmpfs_opts"} $verbose tmpfs "$tmpdir" || { cmd rmdir "$tmpdir"; die; }
+cmd mkfifo "$tmpdir/fifo" || { cmd umount "$tmpdir"; cmd rmdir "$tmpdir"; die; }
# subshell allows cleanup after overmount /tmp without using realpath source
# subshell also avoids trapping and re-raising signals which is annoying in shell
(
- cd "$tmpdir" || die
+ cmd cd "$tmpdir" || die
trap '' INT || die
exec <fifo || die
# read returns non-zero when write end is closed
@@ -144,10 +138,10 @@ mkfifo "$tmpdir/fifo" || { umount "$tmpdir"; rmdir "$tmpdir"; die; }
logv 'unmounting tmpdir'
# TODO: this *almost* works except umount insists on canonicalizing .
# shellcheck disable=SC2086
- umount -cil $no_mtab . || die
+ cmd umount -cil $no_mtab . || die
logv 'deleting tmpdir'
- cd /
- rmdir "$tmpdir" || die
+ cmd cd /
+ cmd rmdir "$tmpdir" || die
) &
# should be FD_CLOEXEC but can't do in shell
exec 9>"$tmpdir/fifo"
@@ -156,17 +150,16 @@ exec 9>"$tmpdir/fifo"
upperdir="$tmpdir/upper"
workdir="$tmpdir/work"
tmpmnt="$tmpdir/tmpmnt"
-logv 'creating dirs'
-mkdir "$upperdir" "$workdir" "$tmpmnt" || die
+cmd mkdir "$upperdir" "$workdir" "$tmpmnt" || die
ovl_opts="lowerdir=$lowerdir,upperdir=$upperdir,workdir=$workdir"
logv 'testing overlay options'
-mount -n -t overlay -o "$ovl_opts" overlay "$tmpmnt" || die "overlayfs is not supported"
-umount "$tmpmnt"
+cmd mount -n -t overlay -o "$ovl_opts" overlay "$tmpmnt" || die "overlayfs is not supported"
+cmd umount "$tmpmnt"
if [ -n "$extra_ovl_opts" ]; then
ovl_opts="$ovl_opts,$extra_ovl_opts"
- mount -n -t overlay -o "$ovl_opts" overlay "$tmpmnt" || die "invalid extra overlayfs options"
- umount "$tmpmnt" || die
+ cmd mount -n -t overlay -o "$ovl_opts" overlay "$tmpmnt" || die "invalid extra overlayfs options"
+ cmd umount "$tmpmnt" || die
fi
chk_ovl_opt() {
if [ -n "$2" ]; then
@@ -187,20 +180,20 @@ try_ovl_opt() {
*",$1[=,]"*) return 0
esac
if chk_ovl_opt "$1"; then
- logv 'not checking %s'
+ logv 'skipping %s' "$1${2+=$2}"
return
fi
- logvn 'trying %s... ' "$1${2+=$2}"
+ logv 'trying %s' "$1${2+=$2}"
new_ovl_opts="$ovl_opts,$1${2+=$2}"
- if ! cmdv mount -n -t overlay -o "$new_ovl_opts" "overlay" "$tmpmnt" 2>/dev/null; then
+ logv "mount -n -t overlay -o '$new_ovl_opts' overlay '$tmpmnt'"
+ if ! mount -n -t overlay -o "$new_ovl_opts" overlay "$tmpmnt" 2>/dev/null; then
[ -z "$verbose" ] || echo rejected >&2
return
fi
- [ -z "$verbose" ] || echo ok >&2
- umount "$tmpmnt" || die
+ cmd umount "$tmpmnt" || die
# clear out workdir/work/incompat/volatile and upperdir index
- rm -r "$workdir" "$upperdir" || die
- mkdir "$upperdir" "$workdir" || die
+ cmd rm -r "$workdir" "$upperdir" || die
+ cmd mkdir "$upperdir" "$workdir" || die
ovl_opts="$new_ovl_opts"
}
try_ovl_opt index on
@@ -221,8 +214,14 @@ owner=$(printf '%s\n' "$ls" | sed -e 's/^[^ ]* [^ ]* \([^ ]*\) \([^ ]*\).*$/\1:\
[ -n "$owner" ] || die 'bad ls owner output'
mode=$(printf '%s\n' "$ls" | sed -e 's/^d\(...\)\(...\)\(...\).*/u=\1,g=\2,o=\3/;s/-//g;t;d')
[ -n "$mode" ] || die 'bad ls mode output'
-cmdv chown "$owner" "$upperdir" || die
-cmdv chmod "$mode" "$upperdir" || die
+if ! cmd chown "$owner" "$upperdir"; then
+ # int sysctl can't be read by read
+ [ "$owner" = "$(dd if=/proc/sys/fs/overflowuid bs=16 status=none):$(dd if=/proc/sys/fs/overflowgid bs=16 status=none)" ] || die
+ read uid_old uid_new uid_cnt < /proc/self/uid_map
+ [ "$uid_old $uid_new" != "0 0" ] || die
+ log 'detected user namespace, ignoring chown failure'
+fi
+cmd chmod "$mode" "$upperdir" || die
# -m - covers ACLs (system.posix_acl_access) and file caps
# (security.capability). theoretically someone might have get/setcap and/or
# get/setfacl but not get/setxattr, but this is unlikely since libcap/acl
@@ -238,7 +237,7 @@ fi
logv 'mounting overlay'
# shellcheck disable=SC2086
-cmdv mount $no_canon $no_mtab $verbose -t overlay -o "$ovl_opts" "${mount_name-overlay}" "$dest" || die
+cmd mount $no_canon $no_mtab $verbose -t overlay -o "$ovl_opts" "${mount_name-overlay}" "$dest" || die
exec 9>&-
wait || die
logv 'done'