[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20250817185048.302679-1-safinaskar@zohomail.com>
Date: Sun, 17 Aug 2025 21:50:48 +0300
From: Askar Safin <safinaskar@...omail.com>
To: cyphar@...har.com
Cc: alx@...nel.org,
autofs@...r.kernel.org,
brauner@...nel.org,
dhowells@...hat.com,
g.branden.robinson@...il.com,
jack@...e.cz,
linux-api@...r.kernel.org,
linux-fsdevel@...r.kernel.org,
linux-kernel@...r.kernel.org,
linux-man@...r.kernel.org,
mtk.manpages@...il.com,
raven@...maw.net,
safinaskar@...omail.com,
viro@...iv.linux.org.uk
Subject: Re: [PATCH v3 00/12] man2: document "new" mount API
I just sent to fsdevel fix for that RESOLVE_NO_XDEV bug.
Aleksa Sarai <cyphar@...har.com>:
> No, LOOKUP_AUTOMOUNT affects all components. I double-checked this with
> Christian.
No. I just tested this. See tests (and miniconfig) in the end of this message.
statx always follows automounts in non-final components no matter what.
I tested this. And it follows automounts in final component depending on
AT_NO_AUTOMOUNT. I tested this too. Also, absolutely all other syscalls always
follow automounts in non-final components no matter what. With sole exception
for openat2 with RESOLVE_NO_XDEV. I didn't test this, but I conclude this
by reading code.
First of all, LOOKUP_PARENT's doc in kernel currently is wrong:
https://elixir.bootlin.com/linux/v6.17-rc1/source/include/linux/namei.h#L31
We see there:
#define LOOKUP_PARENT BIT(10) /* Looking up final parent in path */
This is not true. LOOKUP_PARENT means that we are resolving any non-final
component. LOOKUP_PARENT is set when we enter link_path_walk, which
is used for resolving everything except for final component.
And LOOKUP_PARENT is cleared when we leave link_path_walk.
Now let's look here:
https://elixir.bootlin.com/linux/v6.17-rc1/source/fs/namei.c#L1447
if (!(lookup_flags & (LOOKUP_PARENT | LOOKUP_DIRECTORY |
LOOKUP_OPEN | LOOKUP_CREATE | LOOKUP_AUTOMOUNT)) &&
We never return -EISDIR in this "if" if we are in non-final component
thanks to LOOKUP_PARENT here. We fall to finish_automount instead.
Again: if this is non-final component, then LOOKUP_PARENT is set, and thus
LOOKUP_AUTOMOUNT is ignored. If this is final component, then LOOKUP_AUTOMOUNT
may affect things.
Code below tests that:
- statx always follows non-final automounts
- statx follow final automounts depending on options
The code doesn't test other syscalls, they can be added if needed.
The code was tested in Qemu on Linux 6.17-rc1.
I'm not trying to insult you in any way.
Again: thank you a lot for your work! For openat2 and for these mans.
Askar Safin
====
miniconfig:
CONFIG_64BIT=y
CONFIG_EXPERT=y
CONFIG_PRINTK=y
CONFIG_PRINTK_TIME=y
CONFIG_TTY=y
CONFIG_VT=y
CONFIG_VT_CONSOLE=y
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_PROC_FS=y
CONFIG_DEVTMPFS=y
CONFIG_SYSFS=y
CONFIG_TMPFS=y
CONFIG_DEBUG_FS=y
CONFIG_USER_EVENTS=y
CONFIG_FTRACE=y
CONFIG_MULTIUSER=y
CONFIG_NAMESPACES=y
CONFIG_USER_NS=y
CONFIG_PID_NS=y
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_BLK_DEV_INITRD=y
CONFIG_RD_GZIP=y
CONFIG_BINFMT_ELF=y
CONFIG_BINFMT_SCRIPT=y
CONFIG_TRACEFS_AUTOMOUNT_DEPRECATED=y
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
====
/*
Author: Askar Safin
Public domain
Make sure your kernel is compiled with CONFIG_TRACEFS_AUTOMOUNT_DEPRECATED=y
If all tests pass, the program
should print "All tests passed".
Any other output means that something gone wrong.
This program requires root in initial user namespace
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <linux/openat2.h>
#define MY_ASSERT(cond) do { \
if (!(cond)) { \
fprintf (stderr, "%s: assertion failed\n", #cond); \
exit (1); \
} \
} while (0)
bool
tracing_mounted (void)
{
struct statx tracing;
if (statx (AT_FDCWD, "/tmp/debugfs/tracing", AT_NO_AUTOMOUNT, 0, &tracing) != 0)
{
perror ("statx tracing");
exit (1);
}
if (!(tracing.stx_attributes_mask & STATX_ATTR_MOUNT_ROOT))
{
fprintf (stderr, "???\n");
exit (1);
}
return tracing.stx_attributes & STATX_ATTR_MOUNT_ROOT;
}
void
mount_debugfs (void)
{
if (mount (NULL, "/tmp/debugfs", "debugfs", 0, NULL) != 0)
{
perror ("mount debugfs");
exit (1);
}
MY_ASSERT (!tracing_mounted ());
}
void
umount_debugfs (void)
{
umount ("/tmp/debugfs/tracing"); // Ignore errors
if (umount ("/tmp/debugfs") != 0)
{
perror ("umount debugfs");
exit (1);
}
}
int
main (void)
{
// Init
{
if (chdir ("/") != 0)
{
perror ("chdir /");
exit (1);
}
if (unshare (CLONE_NEWNS) != 0)
{
perror ("unshare");
exit (1);
}
if (mount (NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0)
{
perror ("mount(NULL, /, NULL, MS_REC | MS_PRIVATE, NULL)");
exit (1);
}
if (mount (NULL, "/tmp", "tmpfs", 0, NULL) != 0)
{
perror ("mount tmpfs");
exit (1);
}
}
if (mkdir ("/tmp/debugfs", 0777) != 0)
{
perror ("mkdir(/tmp/debugfs)");
exit (1);
}
// statx always follows automounts in non-final components. With AT_NO_AUTOMOUNT and without AT_NO_AUTOMOUNT
{
mount_debugfs();
{
struct statx readme;
if (statx (AT_FDCWD, "/tmp/debugfs/tracing/README", 0, 0, &readme) != 0)
{
perror ("statx");
exit (1);
}
}
MY_ASSERT (tracing_mounted ());
umount_debugfs();
mount_debugfs();
{
struct statx readme;
if (statx (AT_FDCWD, "/tmp/debugfs/tracing/README", AT_NO_AUTOMOUNT, 0, &readme) != 0)
{
perror ("statx");
exit (1);
}
}
MY_ASSERT (tracing_mounted ());
umount_debugfs();
}
// statx follows automounts in final components if AT_NO_AUTOMOUNT is not specified
{
mount_debugfs();
{
struct statx tracing;
if (statx (AT_FDCWD, "/tmp/debugfs/tracing", 0, 0, &tracing) != 0)
{
perror ("statx");
exit (1);
}
if (!(tracing.stx_attributes_mask & STATX_ATTR_MOUNT_ROOT))
{
fprintf (stderr, "???\n");
exit (1);
}
// Checking that this is new mount, not automount point itself
MY_ASSERT (tracing.stx_attributes & STATX_ATTR_MOUNT_ROOT);
}
MY_ASSERT (tracing_mounted ());
umount_debugfs ();
mount_debugfs();
{
struct statx tracing;
if (statx (AT_FDCWD, "/tmp/debugfs/tracing", AT_NO_AUTOMOUNT, 0, &tracing) != 0)
{
perror ("statx");
exit (1);
}
if (!(tracing.stx_attributes_mask & STATX_ATTR_MOUNT_ROOT))
{
fprintf (stderr, "???\n");
exit (1);
}
MY_ASSERT (!(tracing.stx_attributes & STATX_ATTR_MOUNT_ROOT));
}
MY_ASSERT (!tracing_mounted ());
umount_debugfs ();
}
printf ("All tests passed\n");
exit (0);
}
Powered by blists - more mailing lists