LD_PRELOAD-ed open () + __xstat () + syslog () приводит к EBADF - PullRequest
2 голосов
/ 17 апреля 2020

Я в Fedora 30 с GLIB C 2.29 и ядром 5.2.18-200.fc30.x86_64

$ rpm -qf /usr/lib64/libc.so.6
glibc-2.29-28.fc30.x86_64

. 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);
}

Это работает во всех случаях, о которых я мог подумать, но не в этом:

$ 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

Однако, если я закомментирую syslog() в пользу fprintf(), это работает:

$ 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

Итак, мои дорогие друзья, как мне отладить, почему использование syslog() приводит к EBADF?

================== ================================================== ====

Обновления:

  1. Невозможно воспроизвести в Fedora 32-бета
  2. Следующая команда также воспроизводит ту же проблему:
    $ LD_PRELOAD=$PWD/liboverride.so bash -c "echo blah | xargs -I{} sh -c 'echo {} | cat'"
    
  3. Интересно, что если я заменю cat на /usr/bin/cat, проблема исчезнет.

========================== ================================================== ==

Обновление: основываясь на ответе Карлоса, я выполнил git раздел на findutils (xargs) и обнаружил, что мой сценарий (непреднамеренно?) Был исправлен с добавлением функции:

commit 40cd25147b4461979c0d992299f2c101f9034f7a
Author: Bernhard Voelker <mail@bernhard-voelker.de>
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

1 Ответ

4 голосов
/ 18 апреля 2020

Ваши переопределенные значения open и __xstat не должны иметь побочных эффектов, которые могут быть замечены запущенным процессом.

Ни один процесс не ожидает, что open или __xstat закроет и снова откроет самый низкий уровень. нумерованный файловый дескриптор, а также то, что он должен быть открыт O_CLOEXE C, но это действительно то, что syslog делает, если обнаруживает, что сокет ведения журнала вышел из строя.

Решение заключается в том, что вы должны вызвать closelog после вызова syslog, чтобы избежать появления побочных эффектов для процесса.

Сценарий сбоя выглядит следующим образом:

  • xargs закрывает стандартный ввод.
  • xargs звонки open или stat.
  • Журнал вызовов liboverride.so syslog, который открывает сокет и получает fd 0 в качестве сокета fd.
  • xargs звонки fork.
  • xargs вызывает dup2 для дублирования правого переданного по каналу fd на stdin и перезаписывает fd 0 новым stdin (ожидается, что больше ничего не могло открыть fd 0)
  • xargs звонить execve но ...
  • xargs звонки stat jus t перед execve
  • вызовами журналирования liboverride.so syslog, и реализация обнаруживает, что sendto не удалось, закрывает fd 0 и повторно открывает fd 0 как сокет fd с помощью O_CLOEXE C и регистрирует сообщение.
  • xargs вызывает execve для запуска rev, и сокет O_CLOEXE C fd, fd 0 закрыт.
  • rev ожидает, что fd 0 будет stdin, но он закрыт и поэтому не может прочитать его и записывает сообщение об ошибке на этот эффект на стандартный вывод (который все еще действителен).

Когда вы пишете оболочки, вы должны позаботиться о том, чтобы избежать таких побочных эффектов. В этом случае относительно просто использовать closelog, но это не всегда так.

В зависимости от вашей версии xargs может быть больше или меньше работы между форком и exe c и так что это может работать, если функция ведения журнала liboverride.os не вызывается до exec.

...