Почему монтирование узлов устройств прерывается с помощью EACCES в корне tmpfs?

Попробуйте сделать так:

awk -F, '{if ($5 != "P") { print $0 }}' /path/to/input-file
5
16.11.2020, 01:29
1 ответ

Что ж, это кажется очень интересным эффектом, который является следствием объединения трех механизмов.

Первый (тривиальный )момент заключается в том, что когда вы перенаправляете что-то в файл, оболочка открывает целевой файл с опцией O_CREAT, чтобы быть уверенным, что файл будет создан, если он еще не существует.

Во-вторых, следует учитывать тот факт, что /tmp/x— это точка монтирования tmpfs, а /tmp/x/y— обычный каталог. Учитывая, что вы монтируете tmpfsбез параметров, разрешения точки монтирования автоматически изменяются, так что она становится доступной для записи во всем мире -и имеет липкий бит (1777, который является обычным набором разрешений для /tmp, так что это выглядит как разумное значение по умолчанию ),в то время как разрешения для /tmp/x/y, вероятно,0755(зависят от вашегоumask).

Наконец, третья часть головоломки — это то, как вы настраиваете пространство имен пользователя :и даете указание unshare(1)сопоставить UID/GID пользователя хоста с тем же UID/GID в новом пространстве имен. Это единственное сопоставление в новом пространстве имен, поэтому попытка преобразовать любой другой UID между родительским/дочерним пространством имен приведет к так -называемому UID переполнения , который по умолчанию 65534nobodyпользователь (см. user_namespaces(7), разделUnmapped user and group IDs). Это делает/dev/null(и его привязанные -монтирования )принадлежащими nobodyвнутри дочернего пользовательского пространства имен (, поскольку нет сопоставления для пользователя узла rootв дочернем пользовательском пространстве имен ):

.
$ ls -l /dev/null
crw-rw-rw- 1 nobody nobody 1, 3 Nov 25 21:54 /dev/null

Объединяя все факты вместе, мы приходим к следующему:echo > /tmp/x/nullпытается открыть существующий файл с опцией O_CREAT, в то время как этот файл находится внутри мирового -липкого каталога, доступного для записи, и принадлежит nobody, который не является владельцем каталога, содержащего его.

Теперь прочтите openat(2)внимательно, слово за словом:

EACCES

Where O_CREAT is specified, the protected_fifos or protected_regular sysctl is enabled, the file already exists and is a FIFO or regular file, the owner of the file is neither the current user nor the owner of the containing directory, and the containing directory is both world- or group-writable and sticky. For details, see the descriptions of /proc/sys/fs/protected_fifos and /proc/sys/fs/protected_regular in proc(5).

Разве это не гениально? Это похоже на наш случай... За исключением того факта, что справочная страница сообщает только об обычных файлах и FIFO и ничего не сообщает об узлах устройств.

Итак, давайте взглянем на код, в котором это реализовано. Мы можем видеть, что, по сути, он сначала проверяет исключительные случаи, которые должны следовать (за первым if), а затем просто запрещает доступ для любого другого случая, если липкий каталог доступен для всего мира -для записи (второе if, первое условие):

static int may_create_in_sticky(umode_t dir_mode, kuid_t dir_uid,
        struct inode * const inode)
{
  if ((!sysctl_protected_fifos && S_ISFIFO(inode->i_mode)) ||
      (!sysctl_protected_regular && S_ISREG(inode->i_mode)) ||
      likely(!(dir_mode & S_ISVTX)) ||
      uid_eq(inode->i_uid, dir_uid) ||
      uid_eq(current_fsuid(), inode->i_uid))
    return 0;

  if (likely(dir_mode & 0002) ||
      (dir_mode & 0020 &&
       ((sysctl_protected_fifos >= 2 && S_ISFIFO(inode->i_mode)) ||
        (sysctl_protected_regular >= 2 && S_ISREG(inode->i_mode))))) {
    const char *operation = S_ISFIFO(inode->i_mode) ?
          "sticky_create_fifo" :
          "sticky_create_regular";
    audit_log_path_denied(AUDIT_ANOM_CREAT, operation);
    return -EACCES;
  }
  return 0;
}

Таким образом, если целевой файл является символьным устройством (, а не обычным файлом или FIFO ), ядро ​​по-прежнему запрещает его открытие с помощью O_CREAT, когда этот файл находится в мире -доступный для записи липкий каталог..

Чтобы доказать, что я правильно нашел причину,мы можем проверить, что проблема исчезает в любом из следующих случаев:

  • монтировать tmpfsс -o mode=777— это не сделает точку монтирования липкой;
  • открыть /tmp/x/nullкак O_WRONLY, но без O_CREATопции (, чтобы проверить это, напишите программу, вызывающую open("/tmp/x/null", O_WRONLY | O_CREAT)и open("/tmp/x/null", O_WRONLY), затем скомпилируйте и запустите ее под strace -e trace=openat, чтобы увидеть возвращенный значения для каждого вызова ).

Я не уверен, следует ли считать такое поведение ошибкой ядра или нет, но документация по openat(2)явно не охватывает все случаи, когда этот системный вызов действительно терпит неудачу с EACCES.

5
18.03.2021, 22:49

Теги

Похожие вопросы