lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<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

Powered by Openwall GNU/*/Linux Powered by OpenVZ