bugfixes, features, documentation, examples, new tool
[hband-tools.git] / root-tools / noshellinject
blob292ad49318c934c26721ff3e644803a08e88d93e
1 #!/bin/bash
3 # This script, noshellinject, creates a mount namespace,
4 # in which common shell commands (/bin/sh, /bin/bash, ...) are bind-mounted over with "notashell".
6 # Using notashell(1) is supposed to prevent shell-injections.
8 # notashell(1) bypasses shell only when the program, which is called by noshellinject, does
9 # directly execute an over-mounted shell (notashell switches NOTASHELL_INTERCEPT environment off).
10 # So in your untrusted-argument-validator script and any of its subprocesses,
11 # sh(1)/bash(1) may be called at your convenience.
12 # This is safe, because it's your control what you pass to them from the validator.
14 # One notable thing in its mechanics is that this re-enablement of real shells
15 # is done not by switching back to the original mount namespace,
16 # because the neglegent program (which calls system(3) inconsiderately) may
17 # switch privilege level so its child process can not switch namespaces,
18 # but by keep calling notashell(1) in guise of sh(1)/bash(1)
19 # but no longer having NOTASHELL_INTERCEPT makes notashell(1) call the real boys
20 # from /var/lib/notashell where they were previously saved (bind-mounted).
21 # Therefore, don't allow users to change NOTASHELL_INTERCEPT environment either.
23 set -e
25 # where to save (bind-mount) read shell executables from /bin
26 real_shells_dir=/var/lib/notashell
27 # may extend if the neglegent program calls something else as shell.
28 # all are presumed to be in /bin.
29 shellnames=(sh dash bash)
31 propagtype()
33 findmnt --noheadings --output PROPAGATION "$1"
36 bind_mount_symlink()
38 local src=$1
39 local target=$2
41 if type bindmount-v2 >/dev/null 2>&1
42 then
43 bindmount-v2 "$src" "$target"
44 elif [ ! -L "$src" ]
45 then
46 mount --bind "$src" "$target"
47 else
48 echo "$0: $src is a symlink, which may unexpectedly leak out to the parent namespace if bind-mounted. stop." >&2
49 return 1
53 if [ "$1" = --inner ]
54 then
55 echo "$0: creating private bind-mount on $real_shells_dir to save real shells" >&2
57 mkdir -p "$real_shells_dir"
58 # bind-mount this dir over itself to be able to make private mounts under it
59 mount --bind "$real_shells_dir" "$real_shells_dir"
60 mount --make-private "$real_shells_dir"
62 for shell in "${shellnames[@]}"
64 # can bind-mount existing paths only
65 [ -f "$real_shells_dir/real-$shell" ] || true > "$real_shells_dir/real-$shell"
66 # save the real shell for later use
67 mount --bind /bin/$shell "$real_shells_dir/real-$shell"
68 done
70 echo "$0: umount $real_shells_dir from the parent namespace" >&2
71 # after sub-mounts are mounted, clean up the parent mount from the parent namespace
72 nsenter -t $PPID -m umount -l "$real_shells_dir"
75 echo "$0: creating private bind-mount on /bin to intercept shells" >&2
77 # bind-mount this dir over itself to be able to make private mounts under it
78 bind_mount_symlink /bin /bin
79 mount --make-private /bin
81 # over-mount shells to be able to intercept "sh -c commandLine" type calls
82 for shell in "${shellnames[@]}"
84 bind_mount_symlink /usr/tool/notashell /bin/$shell
85 done
87 echo "$0: umount /bin from the parent namespace" >&2
88 # after sub-mounts are mounted, clean up the parent mount from the parent namespace
89 nsenter -t $PPID -m umount -l /bin
91 shift
92 export NOTASHELL_INTERCEPT=1
93 exec "$@"
94 else
95 propagation=`propagtype /`
96 if [ "$propagation" != shared ]
97 then
98 echo "$0: mount events propagation of the root directory is $propagation, not shared." >&2
99 exit 1
102 echo "$0: creating new mount-namespace to intercept shell executions" >&2
104 exec unshare --mount --propagation=shared -- "$0" --inner "$@"