From d1234611278051941dc03a128cf715295c301f7c Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Wed, 26 Mar 2014 14:02:42 +0100 Subject: [PATCH] some file system based addresses failed to apply file options --- CHANGES | 5 + test.sh | 378 +++++++++++++++++++++++++++++++++++++++++++++++++++- xio-named.c | 4 +- xio-pipe.c | 5 +- xio-unix.c | 64 ++++++--- 5 files changed, 435 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index dd94cd8..16fdfd1 100644 --- a/CHANGES +++ b/CHANGES @@ -44,6 +44,11 @@ corrections: fixed some typos and minor issues, including: Red Hat issue 1021967: formatting error in manual page + UNIX-LISTEN with fork option did not remove the socket file system entry + when exiting. Other file system based passive address types had similar + issues or failed to apply options umask, user e.a. + Thanks to Lorenzo Monti for pointing me to this issue + fixed bug in xio-openssl.c that prevented error handling of bad number of arguments, thanks to Paulik Tamas for reporting diff --git a/test.sh b/test.sh index 48e056d..525eddd 100755 --- a/test.sh +++ b/test.sh @@ -52,7 +52,7 @@ esac #SOCAT_EGD="egd=/dev/egd-pool" MISCDELAY=1 [ -z "$SOCAT" ] && SOCAT="./socat" -if [ ! -x "$SOCAT" ]; then +if ! [ -x "$SOCAT" ] && ! type $SOCAT >/dev/null 2>&1; then echo "$SOCAT does not exist" >&2; exit 1; fi [ -z "$PROCAN" ] && PROCAN="./procan" @@ -165,6 +165,27 @@ DragonFly) IFCONFIG=/sbin/ifconfig ;; *) IFCONFIG=/sbin/ifconfig ;; esac +# need output like "644" +case "$UNAME" in + Linux) fileperms() { stat -L --print "%a\n" "$1" 2>/dev/null; } ;; + FreeBSD) fileperms() { stat -L -x "$1" |grep ' Mode:' |sed 's/.* Mode:[[:space:]]*([0-9]\([0-7][0-7][0-7]\).*/\1/'; } ;; + *) fileperms() { + local p s=0 c + p="$(ls -l -L "$1" |awk '{print($1);}')" + p="${p:1:9}" + while [ "$p" ]; do c=${p:0:1}; p=${p:1}; [ "x$c" == x- ]; let "s=2*s+$?"; done + printf "%03o\n" $s; + } ;; +esac + +# need user (owner) of filesystem entry +case "$UNAME" in + Linux) fileuser() { stat -L --print "%U\n" "$tsock" 2>/dev/null; } ;; + FreeBSD) fileuser() { ls -l test.sh |awk '{print($3);}'; } ;; + *) fileuser() { ls -l test.sh |awk '{print($3);}'; } ;; +esac + + # for some tests we need a second local IPv4 address case "$UNAME" in Linux) @@ -11725,6 +11746,361 @@ PORT=$((PORT+1)) N=$((N+1)) +############################################################################### +# tests: option umask with "passive" NAMED group addresses +while read addr fileopt addropts proto diropt ADDR2; do +if [ -z "$addr" ] || [[ "$addr" == \#* ]]; then continue; fi +# some passive (listening...) filesystem based addresses did not implement the +# umask option +ADDR=${addr^^*} +ADDR_=${ADDR/-/_} +PROTO=${proto^^*} +if [ "$diropt" = "." ]; then diropt=; fi +if [ "$fileopt" = "." ]; then fileopt=; fi +if [ "$addropts" = "." ]; then addropts=; fi +NAME=${ADDR_}_UMASK +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%proto%*|*%socket%*|*%$proto%*|*%$NAME%*) +TEST="$NAME: $ADDR applies option umask" +# start a socat process with passive/listening file system entry. Check the +# permissions of the FS entry, then terminate the process. +# Test succeeds when FS entry exists and has expected permissions. +if ! eval $NUMCOND; then :; else + if [ $ADDR = PTY ]; then set -xv; fi +tlog="$td/test$N.log" +te0="$td/test$N.0.stderr" +tsock="$td/test$N.sock" +if [ -z "$fileopt" ]; then + CMD0="$SOCAT $opts $diropt $ADDR:$tsock,$addropts,unlink-close=0,umask=177 $ADDR2" +else + CMD0="$SOCAT $opts $diropt $ADDR,$fileopt=$tsock,$addropts,unlink-close=0,umask=177 $ADDR2" +fi +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"$te0" & +pid0=$! +wait${proto} $tsock 1 2>"$tlog" +ERRNOENT=; if ! [ -e "$tsock" ]; then ERRNOENT=1; fi +perms=$(fileperms "$tsock") +kill $pid0 2>>"$tlog" +wait +if [ "$ERRNOENT" ]; then + $PRINTF "${RED}no entry${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numFAIL=numFAIL+1 + listFAIL="$listFAIL $N" +elif [ "$perms" != "600" ]; then + $PRINTF "${RED}perms \"$perms\", expected \"600\" ${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + let numFAIL=numFAIL+1 + listFAIL="$listFAIL $N" +else + $PRINTF "$OK\n" + let numOK=numOK+1 +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) +# +done <<<" +# address fileopt addropts waitfor direction ADDR2 +create . . file -U FILE:/dev/null +open . creat file . FILE:/dev/null +gopen . creat file . FILE:/dev/null +unix-listen . . unixport . FILE:/dev/null +unix-recvfrom . . unixport . FILE:/dev/null +unix-recv . . unixport -u FILE:/dev/null +pipe . . file -u FILE:/dev/null +# pty does not seem to honor umask: +#pty link . file . PIPE +" + + +# tests: option perm with "passive" NAMED group addresses +while read addr fileopt addropts proto diropt; do +if [ -z "$addr" ] || [[ "$addr" == \#* ]]; then continue; fi +# test if passive (listening...) filesystem based addresses implement option perm +ADDR=${addr^^*} +ADDR_=${ADDR/-/_} +PROTO=${proto^^*} +if [ "$diropt" = "." ]; then diropt=; fi +if [ "$fileopt" = "." ]; then fileopt=; fi +if [ "$addropts" = "." ]; then addropts=; fi +NAME=${ADDR_}_PERM +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%proto%*|*%socket%*|*%$proto%*|*%$NAME%*) +TEST="$NAME: $ADDR applies option perm" +# start a socat process with passive/listening file system entry. Check the +# permissions of the FS entry, then terminate the process. +# Test succeeds when FS entry exists and has expected permissions. +if ! eval $NUMCOND; then :; else +tlog="$td/test$N.log" +te0="$td/test$N.0.stderr" +tsock="$td/test$N.sock" +#set -vx +if [ -z "$fileopt" ]; then + CMD0="$SOCAT $opts $diropt $ADDR:$tsock,$addropts,perm=511 FILE:/dev/null,ignoreeof" +else + CMD0="$SOCAT $opts $diropt $ADDR,$fileopt=$tsock,$addropts,perm=511 FILE:/dev/null,ignoreeof" +fi +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"$te0" & +pid0=$! +wait${proto} $tsock 1 2>"$tlog" +ERRNOENT=; if ! [ -e "$tsock" ]; then ERRNOENT=1; fi +perms=$(fileperms "$tsock") +kill $pid0 2>>"$tlog" +wait +if [ "$ERRNOENT" ]; then + $PRINTF "${RED}no entry${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numFAIL=numFAIL+1 + listFAIL="$listFAIL $N" +elif [ "$perms" != "511" ]; then + $PRINTF "${RED}perms \"$perms\", expected \"511\" ${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + let numFAIL=numFAIL+1 + listFAIL="$listFAIL $N" +else + $PRINTF "$OK\n" + let numOK=numOK+1 +fi + set +vx +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) +# +done <<<" +# address fileopt addropts waitfor direction +create . . file -U +open . creat file . +gopen . creat file . +unix-listen . . unixport . +unix-recvfrom . . unixport . +unix-recv . . unixport -u +pipe . . file -u +pty link . file . +" + + +# tests: option user with "passive" NAMED group addresses +while read addr fileopt addropts proto diropt; do +if [ -z "$addr" ] || [[ "$addr" == \#* ]]; then continue; fi +# test if passive (listening...) filesystem based addresses implement option user +ADDR=${addr^^*} +ADDR_=${ADDR/-/_} +PROTO=${proto^^*} +if [ "$diropt" = "." ]; then diropt=; fi +if [ "$fileopt" = "." ]; then fileopt=; fi +if [ "$addropts" = "." ]; then addropts=; fi +NAME=${ADDR_}_USER +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%proto%*|*%socket%*|*%$proto%*|*%root%*|*%$NAME%*) +TEST="$NAME: $ADDR applies option user" +# start a socat process with passive/listening file system entry with user option. +# Check the owner of the FS entry, then terminate the process. +# Test succeeds when FS entry exists and has expected owner. +if ! eval $NUMCOND; then :; +elif [ $(id -u) -ne 0 -a "$withroot" -eq 0 ]; then + $PRINTF "test $F_n $TEST... ${YELLOW}must be root${NORMAL}\n" $N + numCANT=$((numCANT+1)) +else +tlog="$td/test$N.log" +te0="$td/test$N.0.stderr" +tsock="$td/test$N.sock" +# set -vx +if [ -z "$fileopt" ]; then + CMD0="$SOCAT $opts $diropt $ADDR:$tsock,$addropts,user=$SUBSTUSER FILE:/dev/null,ignoreeof" +else + CMD0="$SOCAT $opts $diropt $ADDR,$fileopt=$tsock,$addropts,user=$SUBSTUSER FILE:/dev/null,ignoreeof" +fi +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"$te0" & +pid0=$! +wait${proto} $tsock 1 2>"$tlog" +ERRNOENT=; if ! [ -e "$tsock" ]; then ERRNOENT=1; fi +user=$(fileuser "$tsock") +kill $pid0 2>>"$tlog" +wait +if [ "$ERRNOENT" ]; then + $PRINTF "${RED}no entry${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numFAIL=numFAIL+1 + listFAIL="$listFAIL $N" +elif [ "$user" != "$SUBSTUSER" ]; then + $PRINTF "${RED}user \"$user\", expected \"$SUBSTUSER\" ${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + let numFAIL=numFAIL+1 + listFAIL="$listFAIL $N" +else + $PRINTF "$OK\n" + let numOK=numOK+1 +fi + set +vx +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) +# +done <<<" +# address fileopt addropts waitfor direction +create . . file -U +open . creat file . +gopen . creat file . +unix-listen . . unixport . +unix-recvfrom . . unixport . +unix-recv . . unixport -u +pipe . . file -u +pty link . file . +" + + +# tests: is "passive" filesystem entry removed at the end? (without fork) +while read addr fileopt addropts proto diropt crit ADDR2; do +if [ -z "$addr" ] || [[ "$addr" == \#* ]]; then continue; fi +# some passive (listening...) filesystem based addresses did not remove the file +# system entry at the end +ADDR=${addr^^*} +ADDR_=${ADDR/-/_} +PROTO=${proto^^*} +if [ "$diropt" = "." ]; then diropt=; fi +if [ "$fileopt" = "." ]; then fileopt=; fi +if [ "$addropts" = "." ]; then addropts=; fi +# $ADDR removes the file system entry when the process is terminated +NAME=${ADDR_}_REMOVE +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%unix%*|*%socket%*|*%$NAME%*) +TEST="$NAME: $ADDR removes socket entry when terminated during accept" +# start a socat process with listening unix domain socket etc. Terminate the +# process and check if the file system socket entry still exists. +# Test succeeds when entry does not exist. +if ! eval $NUMCOND; then :; else +tlog="$td/test$N.log" +te0="$td/test$N.0.stderr" +tsock="$td/test$N.sock" +if [ -z "$fileopt" ]; then + CMD0="$SOCAT $opts $diropt $ADDR:$tsock,$addropts $ADDR2" +else + CMD0="$SOCAT $opts $diropt $ADDR,$fileopt=$tsock,$addropts $ADDR2" +fi +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"$te0" & +pid0=$! +wait${proto} "$crit" $tsock 1 2>"$tlog" +kill $pid0 2>>"$tlog" +rc1=$? +wait >>"$tlog" +if [ $rc1 != 0 ]; then + $PRINTF "${YELLOW}setup failed${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numCANT=numCANT+1 +elif ! [ $crit $tsock ]; then + $PRINTF "$OK\n" + let numOK=numOK+1 +else + $PRINTF "$FAILED\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numFAIL=numFAIL+1 + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) +# +done <<<" +# address fileopt addropts waitfor direction crit ADDR2 +unix-listen . . unixport . -e FILE:/dev/null +unix-recvfrom . . unixport . -e FILE:/dev/null +unix-recv . . unixport -u -e FILE:/dev/null +pipe . . file -u -e FILE:/dev/null +pty link . file . -L PIPE +" + + +# tests: is "passive" filesystem entry removed at the end? (with fork) +while read addr fileopt addropts proto diropt crit ADDR2; do +if [ -z "$addr" ] || [[ "$addr" == \#* ]]; then continue; fi +# some passive (listening...) filesystem based addresses with fork did not remove +# the file system entry at the end +ADDR=${addr^^*} +ADDR_=${ADDR/-/_} +PROTO=${proto^^*} +if [ "$diropt" = "." ]; then diropt=; fi +if [ "$fileopt" = "." ]; then fileopt=; fi +if [ "$addropts" = "." ]; then addropts=; fi +# $ADDR with fork removes the file system entry when the process is terminated +NAME=${ADDR_}_REMOVE_FORK +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%unix%*|*%socket%*|*%$NAME%*) +TEST="$NAME: $ADDR with fork removes socket entry when terminated during accept" +# start a socat process with listening unix domain socket etc and option fork. +# Terminate the process and check if the file system socket entry still exists. +# Test succeeds when entry does not exist. +if ! eval $NUMCOND; then :; else +tlog="$td/test$N.log" +te0="$td/test$N.0.stderr" +tsock="$td/test$N.sock" +if [ -z "$fileopt" ]; then + CMD0="$SOCAT $opts $diropt $ADDR:$tsock,fork,$addropts $ADDR2" +else + CMD0="$SOCAT $opts $diropt $ADDR,fork,$fileopt=$tsock,$addropts $ADDR2" +fi +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"$te0" & +pid0=$! +wait${proto} "$crit" $tsock 1 2>"$tlog" +kill $pid0 2>>"$tlog" +rc1=$? +wait +if [ $rc1 != 0 ]; then + $PRINTF "${YELLOW}setup failed${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numCANT=numCANT+1 +elif ! [ $crit $tsock ]; then + $PRINTF "$OK\n" + let numOK=numOK+1 +else + $PRINTF "$FAILED\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numFAIL=numFAIL+1 + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) +# +done <<<" +# address fileopt addropts waitfor direction crit ADDR2 +unix-listen . . unixport . -e FILE:/dev/null +unix-recvfrom . . unixport . -e FILE:/dev/null +" + + ############################################################################### # here come tests that might affect your systems integrity. Put normal tests # before this paragraph. diff --git a/xio-named.c b/xio-named.c index 874c8c9..fbb9f68 100644 --- a/xio-named.c +++ b/xio-named.c @@ -24,7 +24,7 @@ const struct optdesc opt_unlink_close = { "unlink-close", NULL, OPT_UNLINK_CLOS const struct optdesc opt_umask = { "umask", NULL, OPT_UMASK, GROUP_NAMED, PH_EARLY, TYPE_MODET, OFUNC_SPEC }; #endif /* WITH_NAMED */ -/* applies to fd all options belonging to phase */ +/* applies to filesystem entry all options belonging to phase */ int applyopts_named(const char *filename, struct opt *opts, unsigned int phase) { struct opt *opt; @@ -137,8 +137,8 @@ int _xioopen_named_early(int argc, const char *argv[], xiofile_t *xfd, } } - applyopts(-1, opts, PH_EARLY); applyopts_named(path, opts, PH_EARLY); + applyopts(-1, opts, PH_EARLY); if (*exists) { applyopts_named(path, opts, PH_PREOPEN); } else { diff --git a/xio-pipe.c b/xio-pipe.c index 6b1e439..43eec01 100644 --- a/xio-pipe.c +++ b/xio-pipe.c @@ -1,5 +1,5 @@ /* source: xio-pipe.c */ -/* Copyright Gerhard Rieger 2001-2008 */ +/* Copyright Gerhard Rieger */ /* Published under the GNU General Public License V.2, see file COPYING */ /* this file contains the source for opening addresses of pipe type */ @@ -96,6 +96,7 @@ static int xioopen_fifo1(int argc, const char *argv[], struct opt *opts, int xio applyopts(-1, opts, PH_INIT); retropt_bool(opts, OPT_UNLINK_EARLY, &opt_unlink_early); + applyopts_named(pipename, opts, PH_EARLY); /* umask! */ applyopts(-1, opts, PH_EARLY); if (opt_unlink_early) { @@ -142,6 +143,8 @@ static int xioopen_fifo1(int argc, const char *argv[], struct opt *opts, int xio } #endif Notice2("created named pipe \"%s\" for %s", pipename, ddirection[rw]); + applyopts_named(pipename, opts, PH_ALL); + } if (opt_unlink_close) { if ((fd->stream.unlink_close = strdup(pipename)) == NULL) { diff --git a/xio-unix.c b/xio-unix.c index 7a35ca6..d8c0a98 100644 --- a/xio-unix.c +++ b/xio-unix.c @@ -166,6 +166,7 @@ static int xioopen_unix_listen(int argc, const char *argv[], struct opt *opts, i if (applyopts_single(xfd, opts, PH_INIT) < 0) return STAT_NORETRY; applyopts(-1, opts, PH_INIT); + applyopts_named(name, opts, PH_EARLY); /* umask! */ applyopts(-1, opts, PH_EARLY); if (!(ABSTRACT && abstract)) { @@ -177,15 +178,27 @@ static int xioopen_unix_listen(int argc, const char *argv[], struct opt *opts, i Error2("unlink(\"%s\"): %s", name, strerror(errno)); } } + } else { + struct stat buf; + if (Lstat(name, &buf) == 0) { + Error1("\"%s\" exists", name); + return STAT_RETRYLATER; + } + } + if (opt_unlink_close) { + if ((xfd->unlink_close = strdup(name)) == NULL) { + Error1("strdup(\"%s\"): out of memory", name); + } + xfd->opt_unlink_close = true; } /* trying to set user-early, perm-early etc. here is useless because file system entry is available only past bind() call. */ - applyopts_named(name, opts, PH_EARLY); /* umask! */ } opts0 = copyopts(opts, GROUP_ALL); + /* this may fork() */ if ((result = xioopen_listen(xfd, xioflags, (struct sockaddr *)&us, uslen, @@ -193,18 +206,15 @@ static int xioopen_unix_listen(int argc, const char *argv[], struct opt *opts, i != 0) return result; - /* we set this option as late as now because we should not remove an - existing entry when bind() failed */ if (!(ABSTRACT && abstract)) { if (opt_unlink_close) { - if (pid == Getpid()) { - if ((xfd->unlink_close = strdup(name)) == NULL) { - Error1("strdup(\"%s\"): out of memory", name); - } - xfd->opt_unlink_close = true; + if (pid != Getpid()) { + /* in a child process - do not unlink-close here! */ + xfd->opt_unlink_close = false; } } } + return 0; } #endif /* WITH_LISTEN */ @@ -380,13 +390,9 @@ int xioopen_unix_recvfrom(int argc, const char *argv[], struct opt *opts, /* only for non abstract because abstract do not work in file system */ retropt_bool(opts, OPT_UNLINK_EARLY, &opt_unlink_early); retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close); - if (opt_unlink_close) { - if ((xfd->unlink_close = strdup(name)) == NULL) { - Error1("strdup(\"%s\"): out of memory", name); - } - xfd->opt_unlink_close = true; - } + } + if (!(ABSTRACT && abstract)) { if (opt_unlink_early) { if (Unlink(name) < 0) { if (errno == ENOENT) { @@ -395,12 +401,30 @@ int xioopen_unix_recvfrom(int argc, const char *argv[], struct opt *opts, Error2("unlink(\"%s\"): %s", name, strerror(errno)); } } + } else { + struct stat buf; + if (Lstat(name, &buf) == 0) { + Error1("\"%s\" exists", name); + return STAT_RETRYLATER; + } } + if (opt_unlink_close) { + if ((xfd->unlink_close = strdup(name)) == NULL) { + Error1("strdup(\"%s\"): out of memory", name); + } + xfd->opt_unlink_close = true; + } + + /* trying to set user-early, perm-early etc. here is useless because + file system entry is available only past bind() call. */ } + applyopts_named(name, opts, PH_EARLY); /* umask! */ xfd->para.socket.la.soa.sa_family = pf; xfd->dtype = XIODATA_RECVFROM_ONE; + + /* this may fork */ return _xioopen_dgram_recvfrom(xfd, xioflags, needbind?(struct sockaddr *)&us:NULL, uslen, @@ -443,6 +467,8 @@ int xioopen_unix_recv(int argc, const char *argv[], struct opt *opts, if (!(ABSTRACT && abstract)) { /* only for non abstract because abstract do not work in file system */ retropt_bool(opts, OPT_UNLINK_EARLY, &opt_unlink_early); + retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close); + if (opt_unlink_early) { if (Unlink(name) < 0) { if (errno == ENOENT) { @@ -451,10 +477,13 @@ int xioopen_unix_recv(int argc, const char *argv[], struct opt *opts, Error2("unlink(\"%s\"): %s", name, strerror(errno)); } } + } else { + struct stat buf; + if (Lstat(name, &buf) == 0) { + Error1("\"%s\" exists", name); + return STAT_RETRYLATER; + } } - - retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close); - if (opt_unlink_close) { if ((xfd->unlink_close = strdup(name)) == NULL) { Error1("strdup(\"%s\"): out of memory", name); @@ -462,6 +491,7 @@ int xioopen_unix_recv(int argc, const char *argv[], struct opt *opts, xfd->opt_unlink_close = true; } } + applyopts_named(name, opts, PH_EARLY); /* umask! */ xfd->para.socket.la.soa.sa_family = pf;