Index: functions.sh =================================================================== --- functions.sh (revision 0) +++ functions.sh (revision 0) @@ -0,0 +1,105 @@ +need_root() +{ + local msg=0 + local msgstr="ERROR: Run these commands as root and restart the command:" + local x + while [ ! -z "$1" ]; do + if [ "$1" = "setuid_prog" ]; then + x=( `ls -ld "$WORK/userchroot"` ) + if [ "${x[2]}" != "root" ]; then + if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi + echo " chown root:root $WORK/userchroot" + echo " chmod u=rwxs,g=x,o=x $WORK/userchroot" + fi + x=( `ls -ld "$WORK/userchroot_owner"` ) + if [ "${x[2]}" != "root" ]; then + if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi + echo " chown root:root $WORK/userchroot_owner" + echo " chmod u=rwxs,g=x,o=x $WORK/userchroot_owner" + fi + fi + if [ "$1" = "devnodes" ]; then + if [ ! -c "$CHROOT/dev/null" ]; then + if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi + echo " mknod -m 666 $CHROOT/dev/null c 1 3" + fi + if [ ! -c "$CHROOT/dev/zero" ]; then + if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi + echo " mknod -m 666 $CHROOT/dev/zero c 1 5" + fi + fi + if [ "$1" = "proc" ]; then + if [ ! -d "$CHROOT/proc/self" ]; then + if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi + echo " mount -t proc chroot_proc $CHROOT/proc" + fi + fi + if [ "$1" = "no_proc" ]; then + if [ -d "$CHROOT/proc/self" ]; then + if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi + echo " umount $CHROOT/proc" + fi + fi + shift + done + if [ $msg -eq 1 ]; then exit 1; fi +} + +chroot_exec() +{ + USER=${chroot_user} HOME=/home "$WORK/userchroot" "$@" +} + +chroot_exec_owner() +{ + USER=`whoami` HOME=/ "$WORK/userchroot_owner" "$@" +} + +clean_dir() +{ + local dir="$1" + local dir_owner="$2" + local only_owner="$3" + local fields + local file + local file_owner + + if [ "$dir_owner" = "$chroot_user" ]; then + chroot_exec / /bin/chmod u+rwx "$dir" + chroot_exec / /bin/ls -1a "$dir" | while read -r file; do + if [ "$file" = "." ] || [ "$file" = ".." ]; then continue; fi + fields=( `chroot_exec / /bin/ls -ld "$dir/$file"` ) + file_owner="${fields[3]}" + if chroot_exec / /usr/bin/test -d "$dir/$file" -a ! -L "$dir/$file"; then + clean_dir "$dir/$file" "$chroot_user" "$only_owner" + if [ -z "$only_owner" ] || [ "$file_owner" = "$chroot_user" ]; then + echo "<< $link" + if [ "${link:0:1}" != "/" ]; then + link="`dirname $file`/$link" + fi + do_file "$target" "$link" + else + if [ -d "$file" ]; then + echo "Dir $file" + elif [ -x "$file" ]; then + echo "Bin $file" + do_libraries "$target" "$file" + else + echo "File $file" + fi + fi +} + +do_libraries() +{ + target="$1" + file="$2" + + ldd "$file" | while read -r LINE; do + fields=( ${LINE//:/ } ) + numfields=${#fields[@]} + + library="" + if [ "${fields[0]}" != "not" ] && [ "${fields[0]}" != "statically" ]; then + if [ $numfields -eq 2 ]; then + library="${fields[0]}" + elif [ $numfields -eq 4 ]; then + library="${fields[2]}" + fi + fi + + if [ ! -z "$library" ]; then + do_file "$target" "$library" + fi + done +} + +initialize_chroot() +{ + if [ -d "$CHROOT" ] || [ -f "$WORK/userchroot" ]; then + echo "ERROR: chroot already exists" + exit 1 + fi + need_root no_proc + + echo "Creating chroot ..." + + # resolve user name into uid and gid + uid="" + owner_uid="" + while read -r LINE; do + fields=( ${LINE//:/ } ) + if [ "${fields[0]}" = "$chroot_user" ]; then + uid="${fields[2]}" + gid="${fields[3]}" + elif [ "${fields[0]}" = `whoami` ]; then + owner_uid="${fields[2]}" + owner_gid="${fields[3]}" + fi + done < /etc/passwd + if [ -z "$uid" ]; then + echo "ERROR: Chroot user '$chroot_user' does not exist" + exit 1 + fi + if [ -z "$owner_uid" ]; then + echo "ERROR: Owner does not exist" + exit 1 + fi + echo "Found user $chroot_user: uid $uid gid $gid" + + if [ -f /etc/gentoo-release ]; then + compiler_string=`gcc-config -c` + fields=( ${compiler_string//-/ } ) + count=${#fields[@]} + gcc_version=${fields[$count-1]} + chost=`echo $compiler_string | sed "s/-${gcc_version}\$//g"` + CHROOT_CONTENTS_DISTRO="/usr/include /usr/share/aclocal /usr/lib/opengl" + CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} /usr/${chost} /usr/libexec/gcc/${chost} /usr/lib/gcc/${chost}/${gcc_version} /usr/lib/binutils/${chost}" + CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} "`echo /usr/bin/${chost}-*` + CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} "`echo /usr/bin/{addr2line,ar,as,c++,c++filt,cpp,g++,gcc,gprof,ld,nm,objcopy,objdump,ranlib,readelf,size,strings,strip}` + CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} "`echo /usr/lib/crt*.o` + CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} "`echo /usr/lib/lib{c,m,dl,pthread}.so /usr/lib/lib{c,pthread}_nonshared.a` + LDPATH_DISTRO="/usr/lib/gcc/${chost}/${gcc_version} "`echo /usr/lib/binutils/${chost}/*` + else + echo "ERROR: Your distribution is not supported" + exit + fi + + # create chroot tree + mkdir -p "$WORK/chroot/"{proc,dev,etc,proc} + mkdir -m 777 "$WORK/chroot/"{home,tmp} + for file in $CHROOT_CONTENTS_BASE $CHROOT_CONTENTS_DEPS $CHROOT_CONTENTS_DISTRO; do + do_file "$WORK/chroot" "$file" + done + for file in $CHROOT_CONTENTS_OPTIONAL; do + do_file "$WORK/chroot" "$file" optional + done + echo -n > "$WORK/chroot/etc/ld.so.conf" + for p in $LDPATH_DISTRO; do + echo "$p" >> "$WORK/chroot/etc/ld.so.conf" + done + /sbin/ldconfig -r "$WORK/chroot" + + # compile setuid chroot wrapper + gcc "-DCHROOT_PATH=\"$WORK/chroot\"" "-DCHROOT_NEWDIR=\"/\"" -DCHROOT_UID=$uid -DCHROOT_GID=$gid -o $WORK/userchroot $TOP/userchroot.c + gcc "-DCHROOT_PATH=\"$WORK/chroot\"" "-DCHROOT_NEWDIR=\"/\"" -DCHROOT_UID=$owner_uid -DCHROOT_GID=$owner_gid -o $WORK/userchroot_owner $TOP/userchroot.c + + echo "Chroot created. To test it, use the 'chroot-shell' command" +} + +clean_chroot() +{ + if [ -d "$CHROOT" ]; then + if [ ! -f "$CHROOT/cleaned" ] && [ -f "$WORK/userchroot" ]; then + need_root setuid_prog devnodes proc + echo "Cleaning foreign files in chroot ..." + clean_dir "/home" `whoami` + clean_dir "/tmp" `whoami` + if [ -d "$CHROOT/$WORK/active" ]; then + clean_dir "/$WORK/active" `whoami` + fi + touch "$CHROOT/cleaned" + fi + need_root no_proc + echo "Removing chroot ..." + rm -rf "$CHROOT" + fi + if [ -f "$WORK/userchroot" ]; then + echo "Removing wrapper ..." + rm "$WORK/userchroot" + fi + + if [ -f "$WORK/userchroot_owner" ]; then + echo "Removing owner wrapper ..." + rm "$WORK/userchroot_owner" + fi +} + +build_wine() +{ + rm -rf "$CHROOT/wine.build" + ln -s wine.orig "$CHROOT/wine.build" + chroot_exec_owner /wine.build /bin/sh -c "./configure" + chroot_exec_owner /wine.build /bin/sh -c "make depend" + chroot_exec_owner /wine.build /bin/sh -c "make -j3" + rm "$CHROOT/wine.build" +} + +build_wine_noconf() +{ + rm -rf "$CHROOT/wine.build" + ln -s wine.orig "$CHROOT/wine.build" + chroot_exec_owner /wine.build /bin/sh -c "make -j3" + rm "$CHROOT/wine.build" +} + initialize_tree() { - cd $WORK - git clone git://source.winehq.org/git/wine.git active - cd active - ./configure - make depend - make -j3 + need_root setuid_prog devnodes proc + echo "Initializing tree ..." + cd "$CHROOT" + rm -rf wine.orig + git clone git://source.winehq.org/git/wine.git wine.orig + build_wine + cd "$WORK" } refresh_tree() { - cd $WORK/active + need_root setuid_prog devnodes proc + echo "Refreshing tree ..." + cd "$CHROOT/wine.orig" # Recover from any accidental damage git diff > git.diff && patch -R -p1 < git.diff # Grab latest source @@ -74,19 +244,21 @@ cat git.log if ! grep -q "Already up-to-date." < git.log then - make -j3 + build_wine_noconf fi + cd "$WORK" } retrieve_patches() { + echo "Retrieving patches ..." cd $PATCHES LAST=`ls *.patch | tail -1 | sed 's/\.patch$//'` NEXT=`expr $LAST + 1` - if ! perl $TOP/get-patches.pl $NEXT - then - echo "No patches to retrieve" - sleep 60 + if ! perl $TOP/get-next-patch.pl $NEXT; then + echo "No patches" + else + echo "Patches retrieved" fi } @@ -142,17 +314,39 @@ cat $NEXT.patch cd $WORK - rm -rf golden - mv active golden - cp -a golden active - cd active - if ! patch -p1 < $PATCHES/$NEXT.patch > $PATCHES/$NEXT.log 2>&1 + + # copy patch to chroot + rm -rf $WORK/chroot/patches + mkdir -p $WORK/chroot/patches + cp $PATCHES/$NEXT.patch $WORK/chroot/patches/ + + # prepare source + if [ -L "$CHROOT/wine.build" ]; then + echo "ERROR: finish original wine build!" + exit 1 + fi + if [ -d "$CHROOT/wine.build" ]; then + echo "Cleaning source in chroot ..." + clean_dir "/wine.build" `whoami` $chroot_user + fi + echo "Syncing source in chroot ..." + mkdir -p "$CHROOT/w ine.build" + rsync -ac "$CHROOT/wine.orig/" "$CHROOT/wine.build" + chmod -R u=rwX,g=rwX,o=rwX "$CHROOT/wine.build" + + # clean home + echo "Cleaning chroot ..." + clean_dir /home `whoami` + clean_dir /tmp `whoami` + + echo "Trying patch ..." + if ! chroot_exec /wine.build /bin/sh -c "patch -p1 < /patches/$NEXT.patch" > $PATCHES/$NEXT.log 2>&1 then report_results patch $PATCHES/$NEXT.patch $PATCHES/$NEXT.log else # TODO: need to run configure? # Note: don't use parallel build, we want to email a nice clean log - if ! make 2>&1 | perl $TOP/trim-build-log.pl >> $PATCHES/$NEXT.log || ! grep "^Wine build complete" $PATCHES/$NEXT.log + if ! chroot_exec /wine.build /bin/sh -c "make" 2>&1 | perl $TOP/trim-build-log.pl >> $PATCHES/$NEXT.log || ! grep "^Wine build complete" $PATCHES/$NEXT.log then report_results build $PATCHES/$NEXT.patch $PATCHES/$NEXT.log else @@ -160,9 +354,6 @@ fi cat $PATCHES/$NEXT.log fi - cd $WORK - rm -rf active - mv golden active return 0 } @@ -175,15 +366,42 @@ retrieve_patches while try_one_patch do - sleep 1 $loop || break done $loop || break + echo "Waiting ..." + sleep 30 done } -if $initialize -then - initialize_tree -fi -continuous_build +while [ ! -z "$1" ]; do + arg="$1" + shift + + if [ "$arg" = "initialize" ]; then + clean_chroot + rm -rf $WORK + mkdir -p $WORK + initialize_chroot + initialize_tree + elif [ "$arg" = "initialize_chroot" ]; then + initialize_chroot + elif [ "$arg" = "clean_chroot" ]; then + clean_chroot + elif [ "$arg" = "chroot-shell" ]; then + need_root setuid_prog devnodes proc + chroot_exec /home /bin/bash + elif [ "$arg" = "chroot-shell-owner" ]; then + need_root setuid_prog devnodes proc + chroot_exec_owner / /bin/bash + elif [ "$arg" = "initialize_tree" ]; then + initialize_tree + elif [ "$arg" = "refresh_tree" ]; then + refresh_tree + elif [ "$arg" = "run" ]; then + continuous_build + else + echo "ERROR: unknown command '$arg'" + exit + fi +done Index: userchroot.c =================================================================== --- userchroot.c (revision 0) +++ userchroot.c (revision 0) @@ -0,0 +1,64 @@ +#include +#include +#include + +// these should be defined at compile time +/* +#define CHROOT_PATH "/home/ambro/chroot" +#define CHROOT_UID 100 +#define CHROOT_GID 100 +*/ + +//#define DEBUG printf +#define DEBUG + +extern char** environ; + +int main(int argc, char** argv) { + DEBUG("starting (pid=%u)\n", (unsigned)getpid()); + + if (argc < 3) { + fprintf(stderr, "usage: $0 [exec-params]\n", argv[0]); + return 1; + } + + DEBUG("uid=%u euid=%u\n", (unsigned)getuid(), (unsigned)geteuid()); + DEBUG("gid=%u egid=%u\n", (unsigned)getgid(), (unsigned)getegid()); + + if (geteuid()!=0 && getegid()!=0) { + fprintf(stderr, "no root privs (suid missing?)\n"); + return 2; + } + + if (chroot(CHROOT_PATH) != 0) { + perror("failed to chroot"); + return 4; + } + + if (chdir(argv[1]) != 0) { + perror("failed to switch working directory"); + return 5; + } + + DEBUG("Restoring privileges\n"); + + if (setgid(CHROOT_GID)!=0 || setuid(CHROOT_UID)!=0) { + fprintf(stderr, "Failed to drop privileges\n"); + return 6; + } + + DEBUG("uid=%u euid=%u\n", (unsigned)getuid(), (unsigned)geteuid()); + DEBUG("gid=%u egid=%u\n", (unsigned)getgid(), (unsigned)getegid()); + + if (access(argv[2], X_OK) != 0) { + perror("target missing?"); + return 7; + } + + if (execve(argv[2], &argv[2], environ) != 0) { + perror("failed to execve"); + return 8; + } + + return 9; +}