Попробуйте сделать так:
awk -F, '{if ($5 != "P") { print $0 }}' /path/to/input-file
Что ж, это кажется очень интересным эффектом, который является следствием объединения трех механизмов.
Первый (тривиальный )момент заключается в том, что когда вы перенаправляете что-то в файл, оболочка открывает целевой файл с опцией O_CREAT
, чтобы быть уверенным, что файл будет создан, если он еще не существует.
Во-вторых, следует учитывать тот факт, что /tmp/x
— это точка монтирования tmpfs
, а /tmp/x/y
— обычный каталог. Учитывая, что вы монтируете tmpfs
без параметров, разрешения точки монтирования автоматически изменяются, так что она становится доступной для записи во всем мире -и имеет липкий бит (1777
, который является обычным набором разрешений для /tmp
, так что это выглядит как разумное значение по умолчанию ),в то время как разрешения для /tmp/x/y
, вероятно,0755
(зависят от вашегоumask
).
Наконец, третья часть головоломки — это то, как вы настраиваете пространство имен пользователя :и даете указание unshare(1)
сопоставить UID/GID пользователя хоста с тем же UID/GID в новом пространстве имен. Это единственное сопоставление в новом пространстве имен, поэтому попытка преобразовать любой другой UID между родительским/дочерним пространством имен приведет к так -называемому UID переполнения , который по умолчанию 65534
— nobody
пользователь (см. 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
.