-r, -R now with CLOEXEC, and warn on write problems

This commit is contained in:
Gerhard Rieger 2023-06-10 11:09:01 +02:00
parent eeeebe6cb2
commit 2dadc1010f
3 changed files with 131 additions and 37 deletions

View file

@ -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.

97
socat.c
View file

@ -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) {

64
test.sh
View file

@ -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