I am on a Fedora 30 box with GLIBC 2.29 and kernel 5.2.18-200.fc30.x86_64
$ rpm -qf /usr/lib64/libc.so.6
glibc-2.29-28.fc30.x86_64
override.c :
#define open Oopen
#define __xstat __Xxstat
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <sys/stat.h>
#include <syslog.h>
#include <errno.h>
#undef open
#undef __xstat
#ifndef DEBUG
#define DEBUG 1
#endif
#define LOG(fmt, ...) \
do { \
if (DEBUG) { \
int errno_ = errno; \
/* fprintf(stderr, "override|%s: " fmt, __func__, __VA_ARGS__); */ \
syslog(LOG_INFO | LOG_USER, "override|%s: " fmt, __func__, __VA_ARGS__); \
errno = errno_; \
} \
} while (0)
/* Function pointers to hold the value of the glibc functions */
static int (*real_open)(const char *str, int flags, mode_t mode);
static int (*real___xstat)(int ver, const char *str, struct stat *buf);
int open(const char *str, int flags, mode_t mode) {
LOG("%s\n", str);
real_open = dlsym(RTLD_NEXT, __func__);
return real_open(str, flags, mode);
}
int __xstat(int ver, const char *str, struct stat *buf) {
LOG("%s\n", str);
real___xstat = dlsym(RTLD_NEXT, __func__);
return real___xstat(ver, str, buf);
}
It works in all cases I could think of, but not this one:
$ gcc -DDEBUG=1 -fPIC -shared -o liboverride.so override.c -ldl -Wall -Wextra -Werror
$ LD_PRELOAD=$PWD/liboverride.so bash -c "echo blah | xargs -I{} sh -c 'echo {} | rev'"
rev: stdin: Bad file descriptor
However, if I comment out syslog()
in favour of fprintf()
, it works:
$ gcc -DDEBUG=1 -fPIC -shared -o liboverride.so override.c -ldl -Wall -Wextra -Werror
$ LD_PRELOAD=$PWD/liboverride.so bash -c "echo blah | xargs -I{} sh -c 'echo {} | rev'"
override|open: /dev/tty
override|__xstat: /tmp/nwani_1587079071
override|__xstat: .
...
... yada ...
... yada ...
... yada ...
...
halb <----------------------------- !
...
... yada ...
... yada ...
... yada ...
override|__xstat: /usr/share/terminfo
So, my dear friends, how do I debug why using syslog()
results into a EBADF
?
=========================================================================
Updates:
$ LD_PRELOAD=$PWD/liboverride.so bash -c "echo blah | xargs -I{} sh -c 'echo {} | cat'"
cat
with /usr/bin/cat
the problem goes away.=========================================================================
Update: Based on Carlos's answer, I ran a git bisect on findutils (xargs) and found that my scenario was (unintentionally?) fixed with a feature addition:
commit 40cd25147b4461979c0d992299f2c101f9034f7a
Author: Bernhard Voelker <[email protected]>
Date: Tue Jun 6 08:19:29 2017 +0200
xargs: add -o, --open-tty option
This option is available in the xargs implementation of FreeBSD, NetBSD,
OpenBSD and in the Apple variant. Add it for compatibility.
* xargs/xargs.c (open_tty): Add static flag for the new option.
(longopts): Add member.
(main): Handle the 'o' case in the getopt_long() loop.
(prep_child_for_exec): Redirect stdin of the child to /dev/tty when
the -o option is given. Furthermore, move the just-opened file
descriptor to STDIN_FILENO.
(usage): Document the new option.
* bootstrap.conf (gnulib_modules): Add dup2.
* xargs/xargs.1 (SYNOPSIS): Replace the too-long list of options by
"[options]" - they are listed later anyway.
(OPTIONS): Document the new option.
(STANDARDS CONFORMANCE): Mention that the -o option is an extension.
* doc/find.texi (xargs options): Document the new option.
(Invoking the shell from xargs): Amend the explanation of the
redirection example with a note about the -o option.
(Viewing And Editing): Likewise.
(Error Messages From xargs): Add the message when dup2() fails.
(NEWS): Mention the new option.
Fixes http://savannah.gnu.org/bugs/?51151
Your overriden open
and __xstat
must not have any side-effects that can be seen by the running process.
No process expects open
or __xstat
to close and reopen the lowest numbered file descriptor, nor that it should be opened O_CLOEXEC, but this is indeed what syslog
does if it finds that the logging socket has failed.
The solution is that you must call closelog
after calling syslog
to avoid any side-effects becoming visible to the process.
The failure scenario looks like this:
open
or stat
.syslog
which opens a socket, and gets fd 0 as the socket fd.fork
.dup2
to dup the right piped fd to stdin, and so overwrites fd 0 with the new stdin (the expectation is that nothing else could have opened fd 0)execve
but...stat
just before execve
syslog
and the implementation detects the sendto
has failed, closes fd 0, and reopens fd 0 as the socket fd with O_CLOEXEC and logs a message.execve
to run rev and the O_CLOEXEC socket fd, fd 0, is closed.When you write wrappers you must take care to avoid such side-effects. In this case it's relatively easy to use closelog
, but that may not always be the case.
Depending on your version of xargs there may be more or less work done between the fork and exec and so it may work if liboverride.os's logging function is not called before the exec
.