From 2dadc1010f404938a529147b0c5b75e918e4d143 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Sat, 10 Jun 2023 11:09:01 +0200 Subject: [PATCH] -r, -R now with CLOEXEC, and warn on write problems --- CHANGES | 7 +++++ socat.c | 97 ++++++++++++++++++++++++++++++++++++++------------------- test.sh | 64 ++++++++++++++++++++++++++++++++++--- 3 files changed, 131 insertions(+), 37 deletions(-) diff --git a/CHANGES b/CHANGES index 1594fb8..dd5d667 100644 --- a/CHANGES +++ b/CHANGES @@ -74,6 +74,13 @@ Coding: fcntl() trace prints flags now in hexadecimal. + Stream dump options -r and -R now open their pathes with CLOEXEC to + prevent leaking into sub processes. + Test: EXEC_SNIFF + + Stream dump write now warn on write errors and partial writes (but + still do not recover). + Porting: Small correction in configure.ac makes Socat C99 able. Thanks to Florian Weimer from Red Hat for providing a patch. diff --git a/socat.c b/socat.c index b2600b4..f65fe75 100644 --- a/socat.c +++ b/socat.c @@ -59,6 +59,7 @@ struct { void socat_usage(FILE *fd); void socat_opt_hint(FILE *fd, char a, char b); void socat_version(FILE *fd); +static int xio_openauxwr(const char *path, const char *hint); int socat(const char *address1, const char *address2); int _socat(void); int cv_newline(unsigned char *buff, ssize_t *bytes, int lineterm1, int lineterm2); @@ -187,21 +188,9 @@ int main(int argc, const char *argv[]) { break; } } - if ((socat_opts.sniffleft = Open(a, O_CREAT|O_WRONLY|O_APPEND| -#ifdef O_LARGEFILE - O_LARGEFILE| -#endif - O_NONBLOCK, 0664)) < 0) { - if (errno == ENXIO) { - if ((socat_opts.sniffleft = Open(a, O_CREAT|O_RDWR|O_APPEND| -#ifdef O_LARGEFILE - O_LARGEFILE| -#endif - O_NONBLOCK, 0664)) > 0) - break; /* try to open pipe rdwr */ - } - Error2("option -r \"%s\": %s", a, strerror(errno)); - } + if ((socat_opts.sniffleft = xio_openauxwr(a, "option -r")) < 0) { + Error2("option -r: failed to open \"%s\": %s", a, strerror(errno)); + } break; case 'R': if (arg1[0][2]) { a = *arg1+2; @@ -212,21 +201,9 @@ int main(int argc, const char *argv[]) { break; } } - if ((socat_opts.sniffright = Open(a, O_CREAT|O_WRONLY|O_APPEND| -#ifdef O_LARGEFILE - O_LARGEFILE| -#endif - O_NONBLOCK, 0664)) < 0) { - if (errno == ENXIO) { - if ((socat_opts.sniffright = Open(a, O_CREAT|O_RDWR|O_APPEND| -#ifdef O_LARGEFILE - O_LARGEFILE| -#endif - O_NONBLOCK, 0664)) > 0) - break; /* try to open pipe rdwr */ - } - Error2("option -R \"%s\": %s", a, strerror(errno)); - } + if ((socat_opts.sniffright = xio_openauxwr(a, "option -R")) < 0) { + Error2("option -R: failed to open \"%s\": %s", a, strerror(errno)); + } break; case 'b': if (arg1[0][2]) { a = *arg1+2; @@ -629,6 +606,47 @@ void socat_version(FILE *fd) { } +/* Opens a path for logging or tracing. + Return the filedescriptor, or -1 when an error occurred (errn0). + Prints messages but no Error(). +*/ +static int xio_openauxwr(const char *path, const char *hint) +{ + int fd; + int _errno; + + if ((fd = Open(path, O_CREAT|O_WRONLY|O_APPEND| +#ifdef O_LARGEFILE + O_LARGEFILE| +#endif +#ifdef O_CLOEXEC + O_CLOEXEC| +#endif + O_NONBLOCK, 0664)) < 0) { + if (errno != ENXIO) + return -1; + /* Possibly a named pipe that does not yet have a reader */ + _errno = errno; + Notice3("%s \"%s\": %s, retrying r/w", hint, path, strerror(errno)); + if ((fd = Open(path, O_CREAT|O_RDWR|O_APPEND| +#ifdef O_LARGEFILE + O_LARGEFILE| +#endif +#ifdef O_CLOEXEC + O_CLOEXEC| +#endif + O_NONBLOCK, 0664)) < 0) { + errno = _errno; + return -1; + } + } +#ifndef O_CLOEXEC + Fcntl_l(fd, F_SETFD, FD_CLOEXEC); +#endif + return fd; +} + + xiofile_t *sock1, *sock2; int closing = 0; /* 0..no eof yet, 1..first eof just occurred, 2..counting down closing timeout */ @@ -1281,6 +1299,7 @@ static int int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe, unsigned char *buff, size_t bufsiz, bool righttoleft) { ssize_t bytes, writt = 0; + ssize_t sniffed; bytes = xioread(inpipe, buff, bufsiz); if (bytes < 0) { @@ -1331,9 +1350,23 @@ int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe, } if (!righttoleft && socat_opts.sniffleft >= 0) { - Write(socat_opts.sniffleft, buff, bytes); + if ((sniffed = Write(socat_opts.sniffleft, buff, bytes)) < bytes) { + if (sniffed < 0) + Warn3("-r: write(%d, buff, "F_Zu"): %s", + socat_opts.sniffleft, bytes, strerror(errno)); + else if (sniffed < bytes) + Warn3("-r: write(%d, buff, "F_Zu") -> "F_Zd, + socat_opts.sniffleft, bytes, sniffed); + } } else if (righttoleft && socat_opts.sniffright >= 0) { - Write(socat_opts.sniffright, buff, bytes); + if ((sniffed = Write(socat_opts.sniffright, buff, bytes)) < bytes) { + if (sniffed < 0) + Warn3("-R: write(%d, buff, "F_Zu"): %s", + socat_opts.sniffright, bytes, strerror(errno)); + else if (sniffed < bytes) + Warn3("-R: write(%d, buff, "F_Zu") -> "F_Zd, + socat_opts.sniffright, bytes, sniffed); + } } if (socat_opts.verbose && socat_opts.verbhex) { diff --git a/test.sh b/test.sh index 6c413d1..f778bd5 100755 --- a/test.sh +++ b/test.sh @@ -15963,7 +15963,16 @@ case "$TESTS" in TEST="$NAME: Socat does not leak FDs to EXEC'd program" # Run Socat with EXEC address, execute Filan to display its file descriptors # Test succeeds when only FDs 0, 1, 2 are in use. -if ! eval $NUMCOND; then :; else +if ! eval $NUMCOND; then :; +elif ! a=$(testaddrs STDIO EXEC); then + $PRINTF "test $F_n $TEST... ${YELLOW}Address $a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! o=$(testoptions stderr) >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else tf="$td/test$N.stdout" te="$td/test$N.stderr" tdiff="$td/test$N.diff" @@ -15974,15 +15983,60 @@ eval "$CMD" >"${tf}" 2>"${te}" # "door" is a special FD type on Solaris/SunOS if [ "$(cat "${tf}" |grep -v ' door ' |wc -l)" -eq 3 ]; then $PRINTF "$OK\n" - if [ "$VERBOSE" ]; then - echo "$CMD" >&2 - fi + if [ "$VERBOSE" ]; then echo "$CMD"; fi + if [ "$DEBUG" ]; then cat "${te}" >&2; fi numOK=$((numOK+1)) else $PRINTF "$FAILED\n" echo "$CMD" >&2 - cat "${tf}" >&2 cat "${te}" >&2 + cat "${tf}" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) + +# Test if Socat makes the sniffing file descriptos (-r, -R) CLOEXEC to not leak +# them to EXEC'd program +NAME=EXEC_SNIFF +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%socket%*|*%filan%*|*%$NAME%*) +TEST="$NAME: Socat does not leak sniffing FDs" +# Run Socat sniffing both directions, with EXEC address, +# execute Filan to display its file descriptors +# Test succeeds when only FDs 0, 1, 2 are in use. +if ! eval $NUMCOND; then :; +elif ! a=$(testaddrs STDIO EXEC); then + $PRINTF "test $F_n $TEST... ${YELLOW}Address $a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! o=$(testoptions stderr) >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD="$TRACE $SOCAT $opts -r $td/test$N.-r -R $td/test$N.-R - EXEC:\"$FILAN -s\",stderr" +printf "test $F_n $TEST... " $N +eval "$CMD" >"${tf}" 2>"${te}" +# "door" is a special FD type on Solaris/SunOS +if [ "$(cat "${tf}" |grep -v ' door ' |wc -l)" -eq 3 ]; then + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD"; fi + if [ "$DEBUG" ]; then cat "${te}" >&2; fi + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD" >&2 + cat "${te}" >&2 + cat "${tf}" >&2 numFAIL=$((numFAIL+1)) listFAIL="$listFAIL $N" fi