From ab74be65e575baacf696e62b420f72e20a2cbc54 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Wed, 12 Feb 2014 17:00:33 +0100 Subject: [PATCH] some file system bases addresses failed to apply file options --- CHANGES | 5 + test.sh | 351 +++++++++++++++++++++++++++++++++++++++++++++++++++- xio-named.c | 6 +- xio-pipe.c | 5 +- xio-unix.c | 64 +++++++--- 5 files changed, 409 insertions(+), 22 deletions(-) diff --git a/CHANGES b/CHANGES index ddcb187..f7c9fb5 100644 --- a/CHANGES +++ b/CHANGES @@ -48,6 +48,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 + porting: Performed changes for Fedora release 19 diff --git a/test.sh b/test.sh index a0f3df1..0a1754b 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" @@ -11302,6 +11302,355 @@ 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 +*%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=$(stat -L --print "%a\n" "$tsock" 2>/dev/null) +kill $pid0 2>>"$tlog" +wait +if [ "$ERRNOENT" ]; then + $PRINTF "${RED}no entry${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numFAIL=numFAIL+1 +elif [ "$perms" != "600" ]; then + $PRINTF "${RED}perms \"$perms\", expected \"600\" ${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + let numFAIL=numFAIL+1 +else + $PRINTF "$OK\n" + let numOK=numOK+1 +fi + set +xv +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 +*%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=$(stat -L --print "%a\n" "$tsock" 2>/dev/null) +kill $pid0 2>>"$tlog" +wait +if [ "$ERRNOENT" ]; then + $PRINTF "${RED}no entry${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numFAIL=numFAIL+1 +elif [ "$perms" != "511" ]; then + $PRINTF "${RED}perms \"$perms\", expected \"511\" ${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + let numFAIL=numFAIL+1 +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 +*%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=$(stat -L --print "%U\n" "$tsock" 2>/dev/null) +kill $pid0 2>>"$tlog" +wait +if [ "$ERRNOENT" ]; then + $PRINTF "${RED}no entry${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + cat "$tlog" + let numFAIL=numFAIL+1 +elif [ "$user" != "$SUBSTUSER" ]; then + $PRINTF "${RED}user \"$user\", expected \"$SUBSTUSER\" ${NORMAL}\n" + echo "$CMD0 &" + cat "$te0" + let numFAIL=numFAIL+1 +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 +*%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 +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 +*%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 +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. # tests must be explicitely selected by roottough or name (not number) diff --git a/xio-named.c b/xio-named.c index c445043..fbb9f68 100644 --- a/xio-named.c +++ b/xio-named.c @@ -1,5 +1,5 @@ /* source: xio-named.c */ -/* Copyright Gerhard Rieger 2001-2011 */ +/* Copyright Gerhard Rieger */ /* Published under the GNU General Public License V.2, see file COPYING */ /* this file contains the source for filesystem entry functions */ @@ -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 bbe40f6..3e6a117 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 */ @@ -100,6 +100,7 @@ static int xioopen_fifo(int argc, const char *argv[], struct opt *opts, int xiof 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) { @@ -146,6 +147,8 @@ static int xioopen_fifo(int argc, const char *argv[], struct opt *opts, int xiof } #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 72de964..a8fbed3 100644 --- a/xio-unix.c +++ b/xio-unix.c @@ -152,6 +152,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)) { @@ -163,15 +164,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, @@ -179,18 +192,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 */ @@ -347,13 +357,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) { @@ -362,12 +368,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, @@ -410,6 +434,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) { @@ -418,10 +444,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); @@ -429,6 +458,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;