openat2() with O_NOFOLLOW failed on arm64

Host environment

  • Operating system:

    Ubuntu 24.04.3 LTS

  • OS/kernel version:

    Linux desktop 6.8.0-90-generic # 91-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 18 14:14:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

  • Architecture:

    x86_64

  • QEMU flavor:

    qemu-aarch64

  • QEMU version:

    binfmt/8bf932d qemu/v10.0.4 go/1.23.12

  • QEMU command line:

    I use QEMU through Docker(tonistiigi/binfmt)

  • OS/kernel version:

    Linux ae1bdda701eb 6.8.0-90-generic #91 (closed)-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 18 14:14:30 UTC 2025 aarch64 GNU/Linux

  • Architecture:

    arm64

Description of problem

openat2() with O_NOFOLLOW failed on arm64. Looks like O_NOFOLLOW got translated wrongly to O_LARGEFILE.

Steps to reproduce

The problem occurred in Github Action using x86_64 to run arm64. I was able to reproduce the problem on my local Ubuntu desktop.

  1. Compile and run a c program calling openat2() with O_NOFOLLOW|O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_PATH

  2. QEMU_STRACE=1 shows

    openat2(-100,"example_dir/",{O_RDONLY|O_DIRECTORY|O_LARGEFILE|O_CLOEXEC|O_PATH,RESOLVE_BENEATH},24) = -1 errno=22 (Invalid argument) 249 
  3. Host Env using strace shows

    $ sudo strace -p <docker_pid> -f -e trace=openat,open,openat2
    [pid 85515] openat2(AT_FDCWD, "example_dir/", {flags=O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_PATH|O_DIRECTORY, resolve=RESOLVE_BENEATH}, 24) = -1 EINVAL (Invalid argument)

Additional information

$ cat mkdirat_openat2.c
#define _LARGEFILE64_SOURCE
#define _GNU_SOURCE
#include <fcntl.h>
#include <linux/openat2.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

/* Wrapper for openat2 syscall */


static int openat2_wrapper(int dirfd, const char *path,
                           struct open_how *how, size_t size)
{
    return syscall(SYS_openat2, dirfd, path, how, size);
}

int main(void)
{
    const char *dirname = "example_dir";
    const char *dirname2 = "example_dir/";

    /* 1. Create directory using mkdirat */
    if (mkdirat(AT_FDCWD, dirname, 0755) == -1) {
        if (errno != EEXIST) {
            perror("mkdirat");
            return 1;
        }
    }

    /* 2. Open directory using openat2 */
    struct open_how how = {
        .flags = O_NOFOLLOW|O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_PATH,
        .mode = 0,
        .resolve = RESOLVE_BENEATH
    };
    printf("Octal of O_NOFOLLOW: %o\n", O_NOFOLLOW);
    printf("Octal of O_LARGEFILE: %o\n", O_LARGEFILE);

    int dfd = openat2_wrapper(
        AT_FDCWD,
        dirname2,
        &how,
        sizeof(how)
    );

    if (dfd == -1) {
        fprintf(stderr, "openat2 failed: errno=%d (%s)\n",
            errno, strerror(errno));
        return 1;
    }

    printf("Directory opened successfully, fd = %d\n", dfd);

    close(dfd);
    return 0;
}
# Guest Env
$ docker run --platform linux/arm64 -v /tmp/code:/tmp/code --rm -it redhat/ubi10-minimal:latest sh

sh-5.2# gcc -Wall -Wextra -O2 mkdirat_openat2.c -o mkdirat_openat2
sh-5.2# QEMU_STRACE=1 ./mkdirat_openat2
...
255 write(1,0x4212a0,28)Octal of O_NOFOLLOW: 100000
 = 28
255 write(1,0x4212a0,24)Octal of O_LARGEFILE: 0
 = 24
openat2(-100,"example_dir/",{O_RDONLY|O_DIRECTORY|O_LARGEFILE|O_CLOEXEC|O_PATH,RESOLVE_BENEATH},24) = -1 errno=22 (Invalid argument) 249 write(2,0x13bff9b8,44)openat2 failed: errno=22 (Invalid argument)
# Host Env
$ sudo strace -p <docker_pid> -f -e trace=openat,open,openat2
[pid 85515] openat2(AT_FDCWD, "example_dir/", {flags=O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_PATH|O_DIRECTORY, resolve=RESOLVE_BENEATH}, 24) = -1 EINVAL (Invalid argument)

Original error in Github Action:

New tar uses openat2 to addresses CVE-2025-45582, and the syscall failed in QEMU (host: x86_64, guest: arm64) .
Looks like calling openat2 with O_NOFOLLOW is the root cause.

https://github.com/trinodb/trino/pull/27867#issuecomment-3718768640

https://gist.github.com/oneonestar/d6d86dbe2508e90658860c8bb049fe7c