fix: drop privileges with a helper of our own, not setpriv
Build docker image and push to registry.bitdeals.org / main-build-job (push) Successful in 1m26s
Build docker image and push to registry.bitdeals.org / main-build-job (push) Successful in 1m26s
Ubuntu 18.04 does not ship setpriv. The program exists in util-linux 2.31, but Debian only began installing it at 2.32, and this image is on bionic because PyBitmessage is Python 2. The build assertion added in the last commit caught it, which is what it is for: `/bin/sh: 1: setpriv: not found`, exit 127, no image pushed. drop_privs.py does the same work with what the image already has. It sets no_new_privs, drops the bounding set with PR_CAPBSET_DROP while CAP_SETPCAP is still held, then optionally becomes the daemon's user. Two shapes, both in run.sh: keep four capabilities and stay root, for the supervisor; keep none and become uid 2000, for the daemon and everything run on its behalf. It is better than setpriv would have been in one respect. The bounding set is walked up to the kernel's own cap_last_cap instead of a list of names, so a capability this image has never heard of goes too -- and the "-all" spelling that bionic's setpriv refuses under a newer kernel is not needed at all. Verified in a user namespace, in the arrangement run.sh uses: the outer drop leaves 00000000000001e0, the inner leaves every capability set at zero with no_new_privs set. The build assertion checks the same two things.
This commit is contained in:
+14
-32
@@ -118,39 +118,19 @@ esc() {
|
||||
printf '%s' "$1" | sed -e 's/[\\&|]/\\&/g'
|
||||
}
|
||||
|
||||
# The fourteen capabilities Docker hands a container by default, written out
|
||||
# rather than as "-all". The keyword is unusable in this image: setpriv refuses
|
||||
# it whenever the running kernel knows more capabilities than the headers
|
||||
# setpriv was built against, and Ubuntu 18.04 (CAP_LAST_CAP 37) under a current
|
||||
# kernel (40) is exactly that case -- it would exit 127 with "libcap-ng is too
|
||||
# old". A capability a compose file adds beyond the default therefore survives,
|
||||
# which is the honest behaviour: nothing here silently drops what it was never
|
||||
# told about. Names carry no cap_ prefix; that spelling is a parse error.
|
||||
BM_CAPS_NONE="-chown,-dac_override,-fowner,-fsetid,-kill,-setgid,-setuid"
|
||||
BM_CAPS_NONE="$BM_CAPS_NONE,-setpcap,-net_bind_service,-net_raw,-sys_chroot"
|
||||
BM_CAPS_NONE="$BM_CAPS_NONE,-mknod,-audit_write,-setfcap"
|
||||
|
||||
# The same list less the four the supervisor keeps: SETUID and SETGID start the
|
||||
# daemon as its own user, KILL is the fallback stop, and SETPCAP is what lets
|
||||
# the daemon's own line above run at all -- dropping a bounding set takes it.
|
||||
BM_CAPS_SUPERVISOR="-chown,-dac_override,-fowner,-fsetid,-net_bind_service"
|
||||
BM_CAPS_SUPERVISOR="$BM_CAPS_SUPERVISOR,-net_raw,-sys_chroot,-mknod"
|
||||
BM_CAPS_SUPERVISOR="$BM_CAPS_SUPERVISOR,-audit_write,-setfcap"
|
||||
|
||||
# Run something as the daemon's own user with nothing left to escalate with: no
|
||||
# capability in any set, an empty bounding set so none can ever be picked up
|
||||
# again, and no_new_privs so that a setuid binary could not help either. This
|
||||
# replaces the earlier privilege drop, which changed the user and left
|
||||
# everything else alone.
|
||||
# everything else alone. What the helper does, and why it is a helper rather
|
||||
# than setpriv, is at the top of drop_privs.py.
|
||||
#
|
||||
# The daemon is the only thing in this container exposed to the network, and
|
||||
# this line is the whole of what it gets. The AppArmor profile this repository
|
||||
# carried until 2022 agrees: the two capabilities it granted served the
|
||||
# AppImage's fuse mount, and PyBitmessage itself asked for none.
|
||||
as_bitmessage() {
|
||||
setpriv --reuid="${USER_UID:-2000}" --regid="${USER_GID:-2000}" \
|
||||
--clear-groups --bounding-set="$BM_CAPS_NONE" \
|
||||
--no-new-privs "$@"
|
||||
/usr/bin/python /usr/local/bin/drop_privs.py --user -- "$@"
|
||||
}
|
||||
|
||||
# --- everything that needs root, and the moment root stops being needed ----
|
||||
@@ -164,13 +144,14 @@ as_bitmessage() {
|
||||
# every start; without FOWNER the chmod stops the container outright.
|
||||
#
|
||||
# So the block ends by re-executing this file with a bounding set that holds
|
||||
# none of the three. What comes back needs SETUID and SETGID to start the daemon
|
||||
# as its own user, and KILL for the supervisor's fallback stop -- three of the
|
||||
# fourteen capabilities Docker hands a container by default, and that is all
|
||||
# there is for the rest of its life, whatever a compromised daemon manages to
|
||||
# execute. The second pass runs the preamble above again: everything it computes
|
||||
# is either read back from an exported variable or recomputed identically, and
|
||||
# the random seed phrase, which is neither, was exported on the first pass.
|
||||
# none of the three. What comes back keeps four capabilities: SETUID and SETGID
|
||||
# to start the daemon as its own user, KILL for the supervisor's fallback stop,
|
||||
# and SETPCAP because emptying the daemon's own bounding set takes it. Four of
|
||||
# the fourteen Docker hands a container by default, and that is all there is for
|
||||
# the rest of its life, whatever a compromised daemon manages to execute. The
|
||||
# second pass runs the preamble above again: everything it computes is either
|
||||
# read back from an exported variable or recomputed identically, and the random
|
||||
# seed phrase, which is neither, was exported on the first pass.
|
||||
if [ -z "${BM_SUPERVISED:-}" ]
|
||||
then
|
||||
# A bind mount hands keys.dat over with the host's ownership on it.
|
||||
@@ -268,10 +249,11 @@ then
|
||||
# Nothing below ever touches another user's file again, so root keeps
|
||||
# only what starting and stopping the daemon takes. The marker is what
|
||||
# stops the second pass repeating this block; the Dockerfile checks
|
||||
# what both of these setpriv lines actually leave behind.
|
||||
# what both of these two drops actually leave behind.
|
||||
BM_SUPERVISED=1
|
||||
export BM_SUPERVISED
|
||||
exec setpriv --bounding-set="$BM_CAPS_SUPERVISOR" /bin/sh "$0"
|
||||
exec /usr/bin/python /usr/local/bin/drop_privs.py \
|
||||
--keep=kill,setgid,setuid,setpcap -- /bin/sh "$0"
|
||||
fi
|
||||
|
||||
# generate address from seed
|
||||
|
||||
Reference in New Issue
Block a user