- #!/bin/bash # Script Name: sandfox
http://igurublog.wordpress.com/downloads/script-sandfox/ #
Requires: inotify-tools # License: GNU GENERAL PUBLIC LICENSE
Version 3 http://www.gnu.org/licenses/gpl-3.0.txt help () {
cat
-
--quiet Minimize output messages NOTES: OPTIONS must precede
COMMAND; you can also use OPTION=VALUE; binds are processed in this
order: bindro bind copy hide; missing binds are ignored; if a
profile for COMMAND exists it will be automatically loaded; default
profile is always loaded; profiles may contain any options valid on
the command line; if COMMAND is omitted, a sandbox will be created
for use. Instructions and updates:
http://igurublog.wordpress.com/downloads/script-sandfox/ EOF exit 0
} log () { output=0 if (( optquiet == 1 )) && [ "$2" =
"quiet" ]; then output=1 elif (( optverbose == 1 )) && [
"$2" = "verb" ]; then output=1 elif (( optquiet == 0 )) &&
[ "$2" != "verb" ]; then output=1 fi if (( output == 1 )); then if
(( optdaemon == 1 )); then d="sandfox-daemon($(basename
"${watchs[0]}")): " else d="" fi echo "$1" if [ "$logfile" != "" ];
then echo "$(date '+%Y-%m-%d %H:%M:%S'): $d$1" >> "$logfile"
fi fi } processopt () { # $1 $2 # Process a command line or profile
option opt="$1" opt2="$2" shifts=2 if [ "${opt:0:2}" = "--" ]; then
opt="${opt:2}" fi case "$opt" in help | -help | -h ) help exit ;;
bind | bindrw | bindro | hide | copy | watch ) if [ "${opt2:0:1}"
!= "/" ]; then log "$curprofile: Option $opt $opt2" "verb" log
"$curprofile: Error: Option $opt requires an absolute path" "quiet"
exit 1 fi if [ "$opt" = "bindrw" ]; then
-
opt="bind" fi eval $opt\s[\$$opt\scnt]=\"\$opt2\" eval \(\(
$opt\scnt += 1 \)\) ;; profile ) if [ "${opt2:0:1}" = "-" ]; then
log "$curprofile: Option $opt $opt2" "verb" log "$curprofile:
Error: Option $opt requires a profile name or file" "quiet" exit 1
fi prof[$profcnt]="$opt2" (( profcnt += 1 )) ;; profilefolder ) if
[ "$profolder" != "" ]; then log "$curprofile: Option $opt $opt2"
"verb" log "$curprofile: Error: Multiple profile folders not
permitted" "quiet" exit 1 elif [ "${opt2:0:1}" != "/" ]; then log
"$curprofile: Option $opt $opt2" "verb" log "$curprofile: Error:
Option $opt requires an absolute path" "quiet" exit 1 fi
profolder="$opt2" ;; sandbox ) if [ "$sandbox" != "" ]; then log
"$curprofile: Option $opt $opt2" "verb" log "$curprofile: Error:
Multiple sandbox names not permitted" "quiet" exit 1 fi
opt2base=`basename "$opt2"` if [ "${opt2:0:1}" = "-" ] || [ "$opt2"
= "" ] || [ "$opt2" != "$opt2base" ]; then log "$curprofile: Option
$opt $opt2" "verb" log "$curprofile: Error: Option $opt requires a
sandbox name" "quiet" exit 1 fi sandbox="$opt2" ;; close )
opt2base=`basename "$opt2"` if [ "${opt2:0:1}" = "-" ] || [ "$opt2"
= "" ] || [ "$opt2" != "$opt2base" ]; then log "$curprofile: Option
$opt $opt2" "verb" log "$curprofile: Error: Option $opt requires a
sandbox name" "quiet" exit 1 fi closename[$closecnt]="$2" ((
closecnt += 1 )) ;; logfile ) if [ "${opt2:0:1}" != "/" ]; then
-
log "$curprofile: Option $opt $opt2" "verb" log "$curprofile:
Error: Option $opt requires an absolute path" "quiet" exit 1 fi if
[ "$logfile" != "" ]; then log "$curprofile: Warning: Multiple log
files ignored" "quiet" else logfile="$opt2" if (( optdaemon == 0
)); then echo
"======================================================================="
>> "$logfile" echo "sandfox started $(date +%Y-%m-%d'
'%H:%M:%S) on $HOSTNAME" >> "$logfile" echo "$logcl" >>
"$logfile" else log "Started >>> $logcl" fi chmod go+rw
"$logfile" 2> /dev/null fi ;; make | verbose | quiet | keepsand
| clean | closeall | status | daemon | shell ) eval opt$opt=1 if ((
optquiet + optverbose == 2 )); then optquiet=0 fi shifts=1 ;; user
) if [ "${opt2:0:1}" = "-" ]; then log "$curprofile: Option $opt
$opt2" "verb" log "$curprofile: Error: Option $opt requires a
username" "quiet" exit 1 fi if [ "$curprofile" = "commandline" ];
then user="$opt2" else log "$curprofile: Warning: Option $opt
ignored in profile" fi ;; * ) log "$curprofile: Option $opt $opt2"
"verb" log "$curprofile: Error: Unknown option $opt" "quiet" exit 1
;; esac if (( shifts == 2 )); then log "$curprofile: Option $opt
$opt2" "verb" else log "$curprofile: Option $opt" "verb" fi return
$shifts } loadprofile () { # $1=profile name or path
-
p="$1" # exists? if [ "${p:0:1}" != "/" ]; then test1=`basename
"$p"` test2=`basename "$p" "profile"` if [ "$test1" = "$test2" ];
then p="$profolder/$p.profile" else p="$profolder/$p" fi fi if [ !
-e "$p" ]; then log "sandfox: Error: Missing profile $p" "quiet"
exit 3 fi # already loaded? loadedprofidx=0 load=1 while ((
loadedprofidx < loadedprofcnt )); do if [ "$p" =
"${loadedprof[$loadedprofidx]}" ]; then load=0 break fi ((
loadedprofidx += 1 )) done if (( load == 1 )); then curprofile="$p"
# global var loadedprof[$loadedprofcnt]="$p" (( loadedprofcnt += 1
)) # read profile log "Loading profile \"$1\"" IFS=$'\n'
loadedopts=`grep -v -e "^[[:blank:]]*#" "$p"` for l in $loadedopts
; do lorg="$l" if [ "$l" != "" ]; then l2=${l#*=} if [ "$l" !=
"$l2" ]; then # split = into two opts l=${l%%=*} else l2="" fi #
remove whitespace and comments while [[ $"${l:0:1}" =~ [[:blank:]]
]]; do l="${l:1}" done l=${l%%#*} lenl=${#l} (( lenl -= 1 )) while
[[ $"${l:lenl:1}" =~ [[:blank:]] ]]; do l="${l:0:lenl}" lenl=${#l}
(( lenl -= 1 )) done l=${l%%[[:blank:]]} while [[ $"${l2:0:1}" =~
[[:blank:]] ]]; do l2="${l2:1}" done l2=${l2%%#*}
-
lenl2=${#l2} (( lenl2 -= 1 )) while [[ $"${l2:lenl2:1}" =~
[[:blank:]] ]]; do l2="${l2:0:lenl2}" lenl2=${#l2} (( lenl2 -= 1 ))
done # process if [ "$l" != "" ]; then processopt "$l" "$l2" elif [
"$l2" != "" ]; then log "sandfox: Syntax error in profile $p"
"quiet" log " Line: $lorg" "quiet" exit 3 fi fi done IFS=" " fi }
randhex4() # generate a four digit random hex number {
rand1=$RANDOM rand2=$RANDOM (( rand = rand1 + rand2 )) let "rand %=
65536" randhex=`printf "%04X" $rand | tr A-Z a-z` if [ "$randhex" =
"" ]; then randhex=$RANDOM # failsafe fi } rmbox () { # $1=mnt
folder to remove if [ "$1" = "" ]; then return elif [ ! -d "$1" ];
then return fi cleanpath="$1" IFS=$'\n' # find dbus daemon(s)
running in sandbox(s) # If DBUS_SESSION_BUS_ADDRESS is not
provided, firefox will # launch a dbus session inside the sandbox.
This needs to be killed # for the sandbox to be closed. if [
"$cleanpath" != "$mnt" ]; then # single sandbox dbuspids=`lsof -w
2> /dev/null | grep $cleanpath/usr/bin/dbus-launch$ | awk
'{print $2}'` else # all sandboxes dbuspids=`lsof -w 2>
/dev/null | grep $cleanpath/.*/usr/bin/dbus-launch$ | awk '{print
$2}'` fi # kill dbus-launch in sandbox(s) for dpid in $dbuspids ;
do log ">>> kill $dpid # kill dbus-launch in sandbox"
"verb" kill $dpid sleep 1
-
tries=0 while (( tries < 20 )) && [ "$(ps -o pid= -p
$dpid)" != "" ]; do if (( tries == 0 )); then log "Waiting for
dbus-launch ($dpid) to terminate..." fi sleep .5 (( tries++ )) done
done # get mount points in reverse order mname="$cleanpath"
mname=${mname//\//\\\/} # convert slashes mtab=`mount | grep " on
$cleanpath\/" | sed "s/.* on \($mname.*\) type .*/\1/" | tac` if [
"$mtab" != "" ]; then # umount in reverse order for m in $mtab ; do
log ">>> umount \"$m\"" "verb" test=`umount "$m"
2>&1` if [ "$?" != "0" ]; then log "$test" log "sandfox:
Error: Closure incomplete - mounts may still exist on" "quiet" log
" $cleanpath Close programs running in" "quiet" log " the sandbox
and try again. If problem persists see output of:" "quiet" log "
lsof -w | grep $cleanpath" exit 4 fi done # do safety checks then
remove folder mounts=`mount | grep " on $cleanpath\/" | sed "s/.*
on \($mname.*\) type .*/\1/"` f1=`find "$cleanpath" -xdev -type f |
wc -l 2> /dev/null` f2=`find "$cleanpath" -type f | wc -l 2>
/dev/null` if [ "$f1" != "$f2" ] && [ "$mounts" = "" ];
then log "sandfox: Error: Not all files could be safely removed
from $cleanpath" "quiet" log " This may indicate a hidden bind
mount still exists. It is" "quiet" log " recommended that you
reboot or backup original folders, then" "quiet" log " manually
remove $cleanpath as root." "quiet" exit 4 elif (( f1 > 50 ))
&& [ "$mounts" = "" ]; then log "sandfox: Error: Not all
files could be safely removed from $cleanpath" "quiet" log "
because the number of files remaining exceeds the safety limit"
"quiet" log " of 50. It is recommended that you reboot or backup
original" "quiet" log " folders, then manually remove $cleanpath as
root." "quiet" exit 4 elif [ "$mounts" != "" ]; then log "sandfox:
Error: Closure incomplete - mounts still exist on" "quiet" log "
$cleanpath Close programs running in" "quiet"
-
log " the sandbox and try again. Mounts:" "quiet" log "$mounts"
"quiet" exit 4 else log "Removing $cleanpath" "verb" log
">>> find \"$cleanpath\" -xdev | sort -r" "verb" f1=`find
"$cleanpath" -xdev | sort -r 2> /dev/null` if [ "$f1" != "" ];
then for f in $f1 ; do if [ -d "$f" ]; then rmdir "$f" else rm "$f"
fi done fi if [ -e "$cleanpath" ]; then log "sandfox: Error: Not
all files could be safely removed from $cleanpath" "quiet" log "
This may indicate a hidden bind mount. It is recommended" "quiet"
log " that you reboot or backup original folders before" "quiet"
log " manually removing $cleanpath as root." "quiet" exit 4 fi fi
fi IFS=" " } mkmount () { # $1=original folder # make mount point
odir="$1" fdir="$sand$1" mkdir -p "$fdir" 2> /dev/null #
recursively copy original folder ownership & permissions while
[ "$odir" != "" ]; do if [ -e "$odir" ]; then chmod
--reference="$odir" "$fdir" 2> /dev/null chown
--reference="$odir" "$fdir" 2> /dev/null else chmod ugo+rwx
"$fdir" 2> /dev/null chown $user:$user "$fdir" 2> /dev/null
fi odir=${odir%/*} fdir=${fdir%/*} done } sandmount () { # $1=type
$2=source $3=target case "$1" in bind | bindro ) if [ -d "$2" ];
then mkmount "$2" elif [ ! -e "$3" ]; then mdir=`dirname "$2"`
mkmount "$mdir" touch "$3"
-
fi log ">>> mount --bind \"$2\" \"$3\"" "verb" mount
--bind "$2" "$3" merr1=$? if [ "$2" != "/dev/random" ] && [
"$2" != "/dev/urandom" ]; then # remount if [ "$1" = "bindro" ];
then if [ "$2" = "/tmp" ]; then log "sandfox: Warning: You are
mounting /tmp read-only - your programs" log " may not work
properly in this sandbox" fi mopt="remount,bind,noatime,nosuid,ro"
else mopt="remount,bind,noatime,nosuid" fi log ">>> mount
-o $mopt \"$3\"" "verb" mount -o $mopt "$3" merr2=$? else # don't
remount /dev/random - causes hang merr2=0 if [ "$1" = "bindro" ];
then log "sandfox: Warning: $2 cannot be mounted ro - mounted rw"
fi fi test=`mount | grep " on $3 type "` if [ "$test" = "" ] || ((
merr1 + merr2 != 0 )); then log "sandfox: Error: $1 mount failed on
$3" "quiet" if (( newsand == 1 )); then rmbox "$sand" fi exit 4 fi
;; tmpfs ) mkmount "$2" if [ -d "$3" ]; then log ">>>
mount -t tmpfs -o noatime,nosuid,size=$tmpfslimit tmpfs \"$3\""
"verb" mount -t tmpfs -o noatime,nosuid,size=$tmpfslimit tmpfs "$3"
merr1=$? test=`mount | grep "^tmpfs on $3 type tmpfs"` if [ "$test"
= "" ] || (( merr1 != 0 )); then log "sandfox: Error: tmpfs mount
failed on $3" "quiet" if (( newsand == 1 )); then rmbox "$sand" fi
exit 4 fi else log "sandfox: Warning: Could not hide $2" fi ;;
esac
-
} boxstatus () { # $1 =box name OR "" for all if [ "$1" = "" ];
then boxes=`find $mnt -maxdepth 1 -mindepth 1 -type d 2>
/dev/null` else boxes="$1" fi IFS=$'\n' log if [ "$boxes" = "" ];
then log " No sandboxes exist" else for b in $boxes ; do
bname="$(basename "$b")" testdaemons=`ps -eo user,cmd | grep -v -e
"grep" -e " /bin/su " | grep -c "sandfox
.*--daemon.*$eventsfolder/$bname[[:blank:]]*$"` testfolders=`find
"$b" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort`
testfolders="${testfolders//$IFS/ }" testmounts=`mount | grep -c "
on $b/"` if [ "$testfolders" != "" ] || [ "$testdaemons" != "" ] ||
(( testmounts > 0 )); then makemsg=" (remake this sandbox to use
it)" if (( testdaemons < 1 )) || [ ! -d "$eventsfolder/$bname"
]; then testdaemons="disabled$makemsg" makemsg="" else testuser=`ps
-eo "%U %a" | grep -v -e "grep" -e " /bin/su " \ | grep -m 1
"sandfox .*--daemon.*$eventsfolder/$bname[[:blank:]]*$" \ | sed
's/\([a-zA-Z0-9]*\) .*/\1/'` testdaemons="running ($testuser)" fi
if (( testmounts < 1 )); then testmounts="none$makemsg"
makemsg="" else testmounts="$testmounts $mnt/$bname/" fi if [
testfolders = "" ]; then testfolders="none$makemsg" fi log
"Sandbox: $bname" log " Daemon: $testdaemons" log " Mounts:
$testmounts" log " Root Dirs: $testfolders" log fi done fi IFS=" "
} mkprofiles () { # profile folder if [ "$profolder" = "" ]; then
profolder="$defaultprofolder"
-
fi mkdir -p "$profolder" if [ ! -d "$profolder" ]; then log
"sandfox: Error: Could not create profile folder $profolder"
"quiet" exit 3 fi if [ ! -e "$profolder/default.profile" ]; then
cat "$profolder/default.profile" # Sandfox Default Profile # #
WARNING: This default profile is loaded for all sandboxes and
should only # contain the minimum folders required by all apps. If
you do not at # least bind /bin /lib and /etc then the chroot
command may not succeed. # # For instructions consult
http://igurublog.wordpress.com/downloads/script-sandfox/ # OPTION #
or # OPTION=VALUE (Do not use quotes) # # To include another
profile in this profile: # profile=PROFILENAME # root folders and
files bindro=/bin # required by chroot su - do not remove
bindro=/etc # required by chroot su - do not remove bindro=/lib #
required by chroot su - do not remove # recommended to keep apps
happy bind=/dev/null bind=/dev/urandom bind=/dev/random
bind=/dev/nvidia0 bind=/dev/nvidiactl bindro=/lib32 bindro=/lib64
bindro=/opt/lib32 bind=/tmp bindro=/usr bindro=/var/lib
hide=/var/lib/mlocate # security # home folders and files #
probably better to bind most home folders and files in another
profile copy=/home/\$user/.bashrc # provides a disposable copy
copy=/home/\$user/.bash_profile # provides a disposable copy #
other folders and files # probably better to put these in another
profile
-
# Lockdown X Access (experimental) # These hides, disabled by
default, MAY HELP to lockdown X access - for # example to
discourage sandboxed apps from taking screen snapshots or # doing
keylogging. If you enable these, be sure to close all sandboxes #
before updating your system. Your package manager won't be able to
# update these files while they are mounted in a sandbox. # #
hide=/usr/bin/import # hide=/usr/bin/xauth # hide=/usr/bin/xev #
hide=/usr/bin/xhost # hide=/usr/bin/xwd #
hide=/usr/bin/xscreensaver # hide=/usr/bin/xscreensaver-command #
hide=/usr/bin/xscreensaver-demo #
hide=/usr/bin/xscreensaver-getimage #
hide=/usr/bin/xscreensaver-getimage-file #
hide=/usr/bin/xscreensaver-getimage-video # hide=/usr/bin/Xorg #
hide=/etc/X11 # hide=/usr/lib/X11 EOF fi if [ ! -e
"$profolder/firefox.profile" ]; then cat
"$profolder/firefox.profile" # Sandfox Firefox Profile # # Note
that default.profile is always loaded in addition to other profiles
# # For instructions consult
http://igurublog.wordpress.com/downloads/script-sandfox/ # OPTION #
or # OPTION=VALUE (Do not use quotes) # # To include another
profile in this profile: # profile=PROFILENAME # root folders and
files required by firefox bindro=/bin bindro=/opt/firefox #
sometimes firefox is installed here bind=/dev/null
bind=/dev/urandom # used by Firefox for security purposes
bind=/dev/random # used by Firefox for printing bind=/dev/nvidia0
bind=/dev/nvidiactl bindro=/etc bindro=/lib bindro=/lib32
bindro=/lib64 bindro=/opt/lib32 bind=/tmp bindro=/usr
bindro=/var/lib hide=/var/lib/mlocate bindro=/run/resolvconf # used
by Firefox for DNS support
-
# required by alsa for Flash sound bindro=/dev/snd # required by
Java bindro=/opt/java bindro=/proc # required by Cups printing in
Firefox bind=/var/cache/cups # Firefox starts faster
bind=/var/cache/fontconfig # Firefox starts faster bind=/var/run #
Firefox shows Cups printers # home folders and files # You may need
to add additional binds to your home folders and files in order #
for every aspect of Firefox to work as you want. Or you can share
your # entire /home/\$user folder (this would reduce security)
bind=/home/\$user/.mozilla bind=/home/\$user/.esd_auth
bind=/home/\$user/.java bindro=/home/\$user/.asoundrc # Needed for
KDE and Gnome themes in Firefox (may be incomplete for gnome) # To
find out what other binds you may need, run 'env' in a shell as
user # and examine the values of GTK2_RC_FILES and GTK_RC_FILES and
XCURSOR_THEME # Note: The bind for kdeglobals below is a limited
privacy risk, as KDE4 stores # recent file and folder names in this
file. You can clean this file with # kscrubber:
http://igurublog.wordpress.com/downloads/script-kscrubber/ # or
don't bind it, but your theme may not work in Firefox
bind=/home/\$user/.config/gtk-2.0 bindro=/home/\$user/.fontconfig
bindro=/home/\$user/.fonts bind=/home/\$user/.gtkrc-2.0
bind=/home/\$user/.gtkrc-2.0-kde4
bind=/home/\$user/.kde/share/config/gtkrc
bind=/home/\$user/.kde/share/config/gtkrc-2.0
bindro=/home/\$user/.kde/share/config/kdeglobals
bind=/home/\$user/.kde4/share/config/gtkrc
bind=/home/\$user/.kde4/share/config/gtkrc-2.0
bindro=/home/\$user/.kde4/share/config/kdeglobals
bindro=/home/\$user/.gtkrc-2.0-kde
bind=/home/\$user/.kde3/share/config/gtkrc
bind=/home/\$user/.kde3/share/config/gtkrc-2.0
bindro=/home/\$user/.kde3/share/config/kdeglobals
bindro=/home/\$user/.Xdefaults # for cursor theme
bindro=/home/\$user/.Xauthority #bindro=/etc/gtk-2.0/gtkrc # used
but already binded all of /etc # Required by flash player for
persisent LSOs # Hide will store the cookies in ram and destroy
them on exit. If you need # LSOs to be permanent, use bind=
instead.
-
#
http://www.wired.com/epicenter/2009/08/you-deleted-your-cookies-think-again/
hide=/home/\$user/.adobe # creates a dummy folder
hide=/home/\$user/.macromedia # creates a dummy folder # other
folders and files # You may want to bind your Downloads or other
data folders below so you # can easily save and upload files from
within Firefox. EOF fi if [ ! -e "$profolder/skype.profile" ]; then
cat "$profolder/skype.profile" # Sandfox Skype Profile # # Note
that default.profile is always loaded in addition to other profiles
# # For instructions consult
http://igurublog.wordpress.com/downloads/script-sandfox/ # OPTION #
or # OPTION=VALUE (Do not use quotes) # # To include another
profile in this profile: # profile=PROFILENAME # Set this to your
Skype video device # Note: /dev/video probably won't work
bind=/dev/video0 bind=/dev/shm bind=/dev/snd bind=/dev/nvidia0
bind=/dev/nvidiactl # bind=/sys/devices/system/cpu # ??? #
bindro=/etc/pulse/client.conf # only needed if /etc not bound
bindro=/proc/interrupts bindro=/var/cache/libx11/compose bind=/tmp
bindro=/usr bind=/usr/share/skype # Gentoo users may need to
disable this bind bind=/opt/skype # Following only needed if all of
/tmp not bound above # copy=/tmp/.ICE-unix # copy=/tmp/.X11-unix/X0
# bind=/tmp/pulse-*/native # Following only needed if all of /usr
not bound above # copy=/usr/bin/skype #
bindro=/usr/lib/qt4/plugins/iconengines #
bindro=/usr/lib/qt4/plugins/imageformats #
bindro=/usr/lib/qt4/plugins/imageformats #
bindro=/usr/lib/qt4/plugins/inputmethods #
bindro=/usr/share/X11/locale # bindro=/usr/share/icons #
bindro=/usr/share/fonts
-
bind=/home/\$user/.Skype bindro=/home/\$user/.ICEauthority
bindro=/home/\$user/.Xauthority
bindro=/home/\$user/.config/Trolltech.conf
bindro=/home/\$user/.fontconfig bindro=/home/\$user/.asoundrc EOF
fi if [ ! -e "$profolder/google-earth.profile" ]; then cat
"$profolder/google-earth.profile" # Sandfox Google-Earth Profile #
# Note that default.profile is always loaded in addition to other
profiles # # For instructions consult
http://igurublog.wordpress.com/downloads/script-sandfox/ # OPTION #
or # OPTION=VALUE (Do not use quotes) # # To include another
profile in this profile: # profile=PROFILENAME # root folders and
files bindro=/bin bind=/dev/null bind=/dev/urandom bind=/dev/random
bind=/dev/nvidia0 bind=/dev/nvidiactl bindro=/etc bindro=/lib
bindro=/lib32 bindro=/lib64 bindro=/opt/lib32 bind=/tmp bindro=/usr
bindro=/var/lib hide=/var/lib/mlocate bindro=/opt/google/earth
bindro=/opt/google-earth # required by Cups printing
bind=/var/cache/cups bind=/var/cache/fontconfig bind=/var/run #
home folders and files # You may need to add additional binds to
your home folders and files in order # for every aspect of
Google-Earth to work as you want. Or you can share your # entire
/home/\$user folder (this would reduce security)
bind=/home/\$user/.googleearth bind=/home/\$user/.config/Google
bind=/home/\$user/.esd_auth
bindro=/home/\$user/.config/Trolltech.conf
-
# Themes bindro=/home/\$user/.Xdefaults
bindro=/home/\$user/.Xauthority bindro=/home/\$user/.fontconfig
bindro=/home/\$user/.fonts # other folders and files # You may want
to bind your Downloads or other data folders below so you # can
easily save and upload files from within Google-Earth. EOF fi if [
! -e "$profolder/default.profile" ] || \ [ ! -e
"$profolder/firefox.profile" ] || \ [ ! -e
"$profolder/google-earth.profile" ] || \ [ ! -e
"$profolder/skype.profile" ]; then log "sandfox: Error: Could not
create default profiles in $profolder" "quiet" exit 3 fi }
######################################################################################
# pre-init defaultprofolder="/etc/sandfox" # BE CAREFUL if you
change $mnt - folder will be removed mnt="/mnt/sandfox" #
eventsfolder cannot contain spaces and must be a bind
eventsfolder="/tmp/sandfox-events" # maximum size of copy/hide
folders tmpfslimit="100M" index=0 bindscnt=0 bindroscnt=0
hidescnt=0 copyscnt=0 watchscnt=0 closecnt=0 prof[0]="default"
profcnt=1 profolder="" sandbox="" logfile="" logcl="$0 $*"
curprofile="commandline" # find chroot wchroot=`which chroot` if [
"$wchroot" = "" ]; then if [ -e /usr/sbin/chroot ]; then
wchroot=/usr/sbin/chroot elif [ -e /sbin/chroot ]; then
wchroot=/sbin/chroot else wchroot=chroot fi
-
fi # parse command line if [ "$1" = "" ]; then help exit fi
while [ "$1" != "" ]; do if [ "${1:0:1}" = "-" ]; then o2=${1#*=}
if [ "$1" != "$o2" ]; then # split = into two opts o1=${1%%=*}
noshift=1 else o1="$1" o2="$2" noshift=0 fi processopt "$o1" "$o2"
if [ "$?" = "2" ] && (( noshift == 0 )); then shift fi else
bcmd="$*" bprog="${bcmd%% *}" bprog="$(basename $bprog)" if [
"$bprog" = "" ]; then log "sandfox: Error: Could not determine
sandbox program" "quiet" exit 1 fi break fi shift done # daemon
mode # Note: Daemon mode is run internally as non-root user by
sandfox to start # an events monitor which starts programs in the
sandbox for the user # Daemon mode is not intended for direct use
by the user if (( optdaemon == 1 )); then if (( watchscnt < 1
)); then log "sandfox-daemon: Error: Invalid daemon call - no
watches" "quiet" exit 9 fi watchx=0 watchlist="" while (( watchx
< watchscnt )); do watchlist="$watchlist \"${watchs[$watchx]}\""
(( watchx += 1 )) done # start watching user=`whoami` IFS=$'\n'
while [ 1 ]; do
-
log ">>> inotifywait -eq modify $watchlist" "verb"
fnotify=`eval /usr/bin/inotifywait -q -e modify -e moved_to -e
create $watchlist` if [ "$?" != "0" ]; then log "Quitting - watch
stopped" exit 0 fi # watch folders still exist? watchx=0 while ((
watchx < watchscnt )); do if [ ! -d "${watchs[$watchx]}" ]; then
log "Quitting - ${watchs[$watchx]} deleted" exit 0 fi (( watchx +=
1 )) done # run f1=`echo "$fnotify" | grep -e " MODIFY " -e "
CREATE " -e " MOVED_TO "` f1="${f1/ CREATE / MODIFY }" f1="${f1/
MOVED_TO / MODIFY }" if [ "$f1" != "" ]; then for fx in $f1 ; do
fdir=${fx%% MODIFY *} f="$fdir${fx#* MODIFY }" ftest1=`basename
"$f"` ftest2=`basename "$f" "desktop"` if [ "$ftest1" != "$ftest2"
]; then desktop=1 else desktop=0 fi if [ -f "$f" ] && [ -O
"$f" ]; then if [ -x "$f" ] && (( desktop == 0)); then log
"Executing $f..." $f & log "Deleting $f..." "verb" ( sleep 5
&& rm -f "$f" ) & elif (( desktop == 1 )); then
wcmd=`cat "$f" | grep -m 1 "^Exec="` if [ "$wcmd" != "" ]; then
wcmd="${wcmd:5}" if [ "$wcmd" != "" ]; then log "Executing $wcmd
($f)..." eval $wcmd & log "Deleting $f..." "verb" ( sleep 5
&& rm -f "$f" ) & else log "Could not execute $f" fi fi
fi fi done fi done exit 12 fi # determine sandbox user
-
runuser=`whoami` if [ "$user" = "" ]; then if [ "$runuser" =
"root" ]; then # based on env if [ "$SUDO_USER$LOGNAME$USER" != ""
]; then for u in $SUDO_USER $LOGNAME $USER ; do if [ "$u" != "root"
] && [ "$u" != "" ] && [ -e "/home/$u" ]; then
user="$u" break fi done fi # based on Xauthority if [ "$user" = ""
] && [ "$XAUTHORITY" != "" ]; then user=`echo "$XAUTHORITY"
| grep "^\/home\/.*\/\.Xauthority$" \ | sed
's/\/home\/\(.*\)\/.*/\1/'` if [ "$user" = "root" ]; then user=""
fi fi # based on /home and ps if [ "$user" = "" ]; then IFS=" "
ucnt=0 keepu="" for d in /home/* ; do u=`basename $d` if [ "$u" !=
"" ] && [ "$u" != "/home/*" ]; then test=`ps h -u $u -o "%U
%c" 2> /dev/null` if [ "$test" != "" ]; then keepu="$u" (( ucnt
+= 1 )) fi fi done if (( ucnt == 1 )) && [ "$keepu" !=
"root" ]; then user="$keepu" fi fi # based on pwd if [ "$user" = ""
]; then if [ "${PWD:0:6}" = "/home/" ]; then u=`echo "$PWD" | sed
's/^\/home\/\([a-z0-9]*\).*/\1/'` if [ "$u" != "" ] && [
"$u" != "root" ] && [ -e "/home/$u" ] ; then user="$u" fi
fi fi else user="$runuser" fi fi if [ "$user" = "root" ]; then log
"sandfox: Warning: Running root in the sandbox is not recommended!"
"quiet" elif [ "$user" = "" ]; then log "sandfox: Error: Could not
determine sandbox user" "quiet"
-
log " Please specify --user USERNAME" "quiet" exit 2 fi # close
if (( optcloseall + closecnt != 0 )); then if [ "$runuser" !=
"root" ]; then log "sandfox: Error: Closing a sandbox requires
root" "quiet" exit 2 fi if (( optcloseall == 1 )); then if [
"$eventsfolder" != "" ]; then log ">>> rm -rf
$eventsfolder/*" "verb" rm -rf $eventsfolder/* # this also stops
daemons sync mkdir -p "$eventsfolder/tmp" chmod ugo+rwx,+t
"$eventsfolder/tmp" 2> /dev/null fi rmbox "$mnt" else closex=0
while (( closex < closecnt )); do c="${closename[$closex]}" #
sandbox exists? testdaemons=`ps -eo user,cmd | grep -v "grep" |
grep "sandfox .*--daemon.*$eventsfolder/$c[[:blank:]]*$"`
testmounts=`mount | grep " on $mnt/$c " | wc -l` if [ -e "$mnt/$c"
] || [ "$testdaemons" != "" ] || (( testmounts > 0 )); then if [
"$eventsfolder" != "" ]; then rm -rf "$eventsfolder/$c" fi rmbox
"$mnt/$c" else log "sandfox: Warning: No such sandbox \"$c\""
"quiet" fi (( closex += 1 )) done fi fi # assume make if ((
bindscnt + bindroscnt + copyscnt + hidescnt != 0 )) || (( profcnt
> 1 )); then optmake=1 elif [ "$sandbox" != "" ] && [
"$bcmd" = "" ]; then optmake=1 fi # count usable daemons and assume
make if (( optmake == 0 )) && [ "$bcmd" != "" ]; then if [
"$sandbox" = "" ]; then daemoncnt=`ps -u $user -o "%U %a" | grep -v
"grep" | grep -c " .*sandfox .*--daemon"` else daemoncnt=`ps -u
$user -o "%U %a" | grep -v "grep" | grep -c "sandfox
.*--daemon.*/$sandbox[[:blank:]]*$"` fi if (( daemoncnt == 0 ));
then
-
log "There are no usable sandbox daemons running for $user -
make has been enabled" optmake=1 fi fi # need root? if [ "$runuser"
!= "root" ] && (( optmake + optcloseall + closecnt +
optshell != 0 )); then log "sandfox: Error: action requires root;
run with sudo or --help for info" "quiet" exit 2 fi # SECTION BELOW
IS ROOT-ONLY if (( optmake == 1 )) && [ "$runuser" = "root"
]; then # create default profiles mkprofiles # load profiles
profidx=0 loadedprofcnt=0 if [ "$bprog" != "" ] && [ -e
"$profolder/$bprog.profile" ]; then prof[$profcnt]="$bprog" ((
profcnt += 1 )) fi while (( profidx < profcnt )); do # Note:
prof[] may grow as profiles are read loadprofile
"${prof[$profidx]}" (( profidx += 1 )) done # sand newsand=0 if [
"$sandbox" != "" ]; then # sandbox exists? filecount=`find
"$mnt/$sandbox" -mindepth 1 2> /dev/null | wc -l`
testdaemons=`ps -eo user,cmd | grep -v "grep" | grep "sandfox
.*--daemon.*/$sandbox[[:blank:]]*$"` testmounts=`mount | grep " on
$mnt/$sandbox" | wc -l` if (( filecount > 0 )) || [
"$testdaemons" != "" ] || (( testmounts > 0 )); then newsand=0
else newsand=1 fi sandname="$sandbox" else if [ "$bprog" = "" ];
then p="${prof[1]}" if [ "${p:0:1}" = "/" ]; then p=`basename "$p"
".profile"` fi if [ "$p" != "" ]; then sandname="$p" else
sandname="default" fi else sandname="$bprog"
-
fi sandnametmp="$sandname" while [ -e "$mnt/$sandname" ]; do
randhex4 sandname="$sandnametmp-$randhex" done newsand=1 fi if ((
newsand == 1 )); then log "Creating new sandbox \"$sandname\"" else
log log "Updating sandbox $sandname" fi sand="$mnt/$sandname" #
default folders mkdir -p "$mnt" chown root:root "$mnt" chmod
go+rx,go-w "$mnt" mkdir "$sand" chown root:root "$sand" chmod
go+rx,go-w "$sand" if [ ! -d "$sand" ]; then log "sandfox: Error:
Could not create sand folder $sand" "quiet" exit 3 fi # check for
required binds bindtmp=0 bindbin=0 bindetc=0 bindlib=0 bindusrlib=0
bindusrbin=0 binddev=0 bidx=0 while (( bidx < bindscnt )); do
case "${binds[$bidx]}" in /tmp ) bindtmp=1 ;; /dev ) binddev=1 ;;
/bin ) bindbin=1 ;; /etc ) bindetc=1 ;; /lib ) bindlib=1 ;; /usr )
bindusrbin=1 bindusrlib=1 ;; /usr/bin ) bindusrbin=1
-
;; /usr/lib ) bindusrlib=1 ;; esac (( bidx += 1 )) done bidx=0
while (( bidx < bindroscnt )); do case "${bindros[$bidx]}" in
/tmp ) bindtmp=1 ;; /dev ) binddev=1 ;; /bin ) bindbin=1 ;; /etc )
bindetc=1 ;; /lib ) bindlib=1 ;; /usr ) bindusrbin=1 bindusrlib=1
;; /usr/bin ) bindusrbin=1 ;; /usr/lib ) bindusrlib=1 ;; esac ((
bidx += 1 )) done if (( bindtmp == 0 )); then if [
"${eventsfolder:0:4}" = "/tmp" ]; then
binds[$bindscnt]="$eventsfolder" (( bindscnt += 1 )) fi log
"sandfox: Warning: Not binding /tmp may cause some programs to fail
or hang" fi if (( binddev == 0 )); then
binds[$bindscnt]="/dev/null" (( bindscnt += 1 )) fi if ((
bindusrbin == 0 )); then
bindros[$bindroscnt]="/usr/bin/inotifywait" (( bindroscnt += 1 ))
bindros[$bindroscnt]="/usr/bin/whoami" (( bindroscnt += 1 ))
bindros[$bindroscnt]="/usr/bin/basename" (( bindroscnt += 1 )) fi
if (( bindusrlib == 0 )); then
-
f1=`find /usr/lib -maxdepth 1 -xtype f -name
"libinotifytools*.so*" 2> /dev/null` if [ "$f1" != "" ]; then
IFS=$'\n' for f in $f1 ; do bindros[$bindroscnt]="$f" (( bindroscnt
+= 1 )) done IFS=" " fi fi if (( bindbin + bindlib + bindetc != 3
)); then log "sandfox: Warning: Not binding /bin /etc and /lib may
not allow" log " the chroot or su commands to run" fi if [ ! -e
"/usr/bin/inotifywait" ]; then log "sandfox: Error:
/usr/bin/inotifywait not found" "quiet" log " Arch use: pacman -S
inotify-tools" "quiet" log " Ubuntu use: apt-get install
inotify-tools" "quiet" exit 3 fi # binds for b in bindro bind copy
hide ; do # step through list of binds to be processed bidx=0 eval
bcnt=$b\scnt while (( bidx < bcnt )); do eval
curb=\"\${$b\s[$bidx]}\" # current bind item
curb=${curb//\$user/$user} log "Processing $b $curb" "verb" if [ -e
"$curb" ] || [ "$b" = "hide" ]; then # check for duplicates
dupefound=0 (( dupeidx = bidx - 1 )) while (( dupeidx > -1 ));
do eval dupeb=\"\${$b\s[$dupeidx]}\" # possible duplicate item
dupeb=${dupeb//\$user/$user} if [ "$dupeb" = "$curb" ]; then
dupefound=1 break fi (( dupeidx -= 1 )) done # process if ((
dupefound == 0 )); then test=`mount | grep " $sand$curb "` if [
"$test" != "" ]; then log "$b $sand$curb: already mounted" "verb"
else case "$b" in bindro | bind ) # create mount point and
recursively copy permissions # bind mount file or folder sandmount
$b "$curb" "$sand$curb" ;; copy | hide )
-
if [ -d "$curb" ] || [ ! -e "$curb" ]; then # folder copy/hide #
mount on tmpfs sandmount tmpfs "$curb" "$sand$curb" if [ -e "$curb"
]; then chmod --reference="$curb" "$sand$curb" 2> /dev/null
chown --reference="$curb" "$sand$curb" 2> /dev/null else #
non-existent hide permissions chmod ugo+rwx "$sand$curb" 2>
/dev/null chown $user:$user "$sand$curb" 2> /dev/null fi if [
"$b" = "copy" ]; then log ">>> cp -ax \"$curb/.\"
\"$sand$curb/.\"" "verb" cp -ax "$curb/." "$sand$curb/." if [ "$?"
!= "0" ]; then log "sandfox: Warning: cp reported an error copying
$curb" "quiet" fi fi else # file copy/hide if [ "$b" = "copy" ];
then # copy if [ ! -e "$sand$curb" ]; then mdir=`dirname "$curb"`
mkmount "$mdir" log ">>> cp -a \"$curb\" \"$sand$curb\""
"verb" cp -a "$curb" "$sand$curb" if [ "$?" != "0" ]; then log
"sandfox: Warning: cp reported an error copying $curb" "quiet" fi
else log "Cannot create copy of $curb" "verb" log " because it
already exists in sandbox" "verb" fi else # hide sandmount bind
"/dev/null" "$sand$curb" fi fi ;; esac fi fi fi (( bidx += 1 ))
done done
-
sync # daemon running for user? test=`ps -u $user -o "%U %a" |
grep -v "grep" \ | grep " .*sandfox .*--daemon
.*$eventsfolder/$sandname[[:blank:]]*$"` if [ "$test" = "" ]; then
# start daemon if [ "$logfile" != "" ]; then dlog="--logfile
$logfile" else dlog="" fi if (( optverbose == 1 )); then
verb="--verbose" else verb="" fi mkdir -p "$eventsfolder" chown
root:root "$eventsfolder" 2> /dev/null chmod ugo+rwx,+t
"$eventsfolder" 2> /dev/null if [ ! -d "$eventsfolder" ]; then
log "sandfox: Error: Could not create events folder $eventsfolder"
"quiet" exit 3 fi mkdir -p "$eventsfolder/tmp" chmod ugo+rwx,+t
"$eventsfolder/tmp" 2> /dev/null cp "$0"
"$eventsfolder/tmp/sandfox" chmod ugo+rx,go-w
"$eventsfolder/tmp/sandfox" mkdir -p "$eventsfolder/$sandname"
chmod ugo+rwx,+t "$eventsfolder/$sandname" log "Starting daemon as
$user for sandbox \"$sandname\"..." "verb" log ">>>
$wchroot $sand /bin/su $user -c \"$eventsfolder/tmp/sandfox
--daemon $dlog $verb --watch $eventsfolder/$sandname\"" "verb" if
(( optverbose == 1 )); then $wchroot $sand /bin/su $user -c
"$eventsfolder/tmp/sandfox --daemon $dlog $verb --watch
$eventsfolder/$sandname" & else $wchroot $sand /bin/su $user -c
"$eventsfolder/tmp/sandfox --daemon $dlog $verb --watch
$eventsfolder/$sandname" 2> /dev/null > /dev/null & fi
sleep .5 test=`ps -u $user -o "%U %a" | grep -v "grep" \ | grep "
.*sandfox .*--daemon .*$eventsfolder/$sandname[[:blank:]]*$"` if [
"$test" = "" ]; then log "sandfox: Warning: Could not start daemon
- you may not be able" "quiet" log " to run additional programs in
this sandbox" "quiet" fi fi fi # run command in sandbox if [
"$bcmd" != "" ]; then
-
if [ "$sandname" = "" ]; then # check/set sandbox name if [
"$sandbox" != "" ]; then sandname="$sandbox" else # find a sandbox
daemon to run bcmd IFS=" " dtotal=0 dprefer="" dgood="" for d in
$eventsfolder/* ; do if [ "$d" != "$eventsfolder/*" ] && [
"$d" != "" ] && [ -d "$d" ] \ && [ "$d" !=
"$eventsfolder/tmp" ] ; then dname=`basename "$d"` test=`ps -u
$user -o "%U %a" | grep -v "grep" \ | grep " .*sandfox .*--daemon
.*$d[[:blank:]]*$"` if [ "$test" != "" ]; then examples="$examples
sandfox --sandbox $dname $bcmd\n" dgood="$dname" if [ "$dname" =
"$bprog" ]; then dprefer="$dname" fi (( dtotal += 1 )) fi fi done
if (( dtotal == 0 )); then log "sandfox: Error: There is no open
sandbox to run $bprog" "quiet" exit 5 elif (( dtotal > 1 ));
then log "sandfox: Warning: There is more than one sandbox open"
log " To specify a sandbox:" echo -e "$examples" if [ "$dprefer" !=
"" ]; then sandname="$dprefer" else sandname="$dgood" fi else
sandname="$dgood" fi fi fi # Check if firefox already running if [
"${bcmd%% *}" = "firefox" ]; then testrunning=`ps -u $user -o "%U
%a" | grep -v "grep" | grep -v "sandfox" \ | grep -e " *${bcmd%%
*}$" -e " *${bcmd%% *} "` if [ "$testrunning" != "" ]; then log
"sandfox: Warning: An instance of ${bcmd%% *} is already running"
fi fi
-
if [ "$runuser" = "root" ]; then # start directly if (( optshell
== 1 )) || [ "$bprog" = "bash" ]; then log if [ "$bprog" = "bash"
]; then p="" else p=" running $bprog" fi log ">>> shell -
you are $user$p in sandbox \"$sandname\" $wchroot $mnt/$sandname
/bin/su $user -c \"$bcmd\"" "verb" if [ "$SUDO_USER" != "" ]; then
ruser="$SUDO_USER" else ruser="ROOT" fi $wchroot $mnt/$sandname
/bin/su $user -c "$bcmd" log log ">" else log "Starting $bprog
as $user in sandbox \"$sandname\"..." log ">>> $wchroot
$mnt/$sandname /bin/su $user -c \"$bcmd\" &" "verb" if ((
verbose == 1 )); then $wchroot $mnt/$sandname /bin/su $user -c
"$bcmd" & else $wchroot $mnt/$sandname /bin/su $user -c "$bcmd"
2> /dev/null > /dev/null & fi fi else log "Starting
$bprog as $user in sandbox \"$sandname\"..." # start via daemon #
get env of caller e=`env | grep -v "'" | sed
"s/\([A-Z_]*=\)\(.*\)/\1\\\'\2\\\'/"` if [ "$e" != "" ]; then
e="env -i ${e//$'\n'/ }" else e="" fi # build start script randhex4
cscript="$bprog-$randhex.sh" while [ -e
"$eventsfolder/tmp/$cscript" ]; do randhex4
cscript="$bprog-$randhex.sh" done cat "$eventsfolder/tmp/$cscript"
#!/bin/bash # sandfox automatic start script # It is safe to delete
this file $e $bcmd & EOF chmod u+x,go-rwx
"$eventsfolder/tmp/$cscript" # move start script to events folder
mv -f "$eventsfolder/tmp/$cscript" "$eventsfolder/$sandname" log
"Wrote start script $cscript ($bcmd)" "verb"
-
fi fi if (( optstatus == 1 )); then boxstatus fi exit #
CHANGELOG: # 1.1.4: chroot discovery # added Firefox and Skype
bindro=/home/\$user/.asoundrc # 1.1.3: added bindro /run/resolvconf
to default Firefox profile # 1.1.2: accomodate change to remount
bind usage # accomodate change to mtab bind mounts showing type #
added /dev/nvidia0 & /dev/nvidiactl binds to default profiles #
added bindro=/opt/firefox to default firefox profile # improved
detection for 'firefox already running' warning # 1.1.1: accomodate
recent change to ps line endings # 1.1.0: kills dbus-launch inside
sandboxes before closing (lsof required) # 1.0.10: use tmpfs
instead of none for mount source (due to new util-linux) # skype
profile adds /opt/skype and disable note for Gentoo users # 1.0.9:
permissions on mnt folders # removed uuidgen dependency # determine
sandbox user with Xauthority # added default google-earth profile #
1.0.8: modification of daemon detection for non-standard ps #
non-existent --sandbox triggers make # corrected --status mount
count # 1.0.7: added /dev/video0 to skype profile (replaces
/dev/video) # added disabled Lockdown X Access section to default
profile # 1.0.6: added firefox bindro for ~/.Xauthority # added
warning if firefox already running # 1.0.5: modification of daemon
detection for non-standard ps # 1.0.4: corrected detection of
running daemon with --verbose and --logfile # 1.0.3: corrected
problems with sandboxes with similar names # 1.0.2: hide
non-existent folder corrected # 1.0.0: handle /dev/urandom like
/dev/random to prevent hangs # 0.9.6: Changed tmpfslimit to 100M #
now allows successful bind=/dev/random for Firefox printing # added
user-contributed skype profile (experimental)