diff mbox series

[v3] linux: Add openat2 (BZ 31664)

Message ID 20250321210124.3688671-1-adhemerval.zanella@linaro.org
State New
Headers show
Series [v3] linux: Add openat2 (BZ 31664) | expand

Commit Message

Adhemerval Zanella March 21, 2025, 9 p.m. UTC
The openat2 syscall was added on Linux 5.6, as an extension of openat.
Different than other open-like functions, the kernel only provides the
LFS variant (so files larger than 4GB always succeed, as other functions
with offset larger than off_t).  Also, imilar to other open functions,
the new symbol is a cancellable entrypoint.

The test case added only stresses some of the syscalls provided
functionalities and it is based on an existent kernel self-test.  Since
the prototype does not use variadic arguments, there is no need to add a
fortify wrapper to catch wrong usages.

The documentation mostly defers to the Linux manual pages.

Checked on x86_64-linux-gnu.


--

This patch follows the recent sched_setattr inclusion on how to handle
the new kABI syscall with extensible fields, where it up to the caller
to not use it where it might case ABI issues with different TU.A

Changes from v2:
* Rabse against master and adapt to version 2.42

---
 NEWS                                          |   4 +
 manual/llio.texi                              | 100 +++++++
 sysdeps/unix/sysv/linux/Makefile              |  14 +
 sysdeps/unix/sysv/linux/Versions              |   3 +
 sysdeps/unix/sysv/linux/aarch64/libc.abilist  |   1 +
 sysdeps/unix/sysv/linux/alpha/libc.abilist    |   1 +
 sysdeps/unix/sysv/linux/arc/libc.abilist      |   1 +
 sysdeps/unix/sysv/linux/arm/be/libc.abilist   |   1 +
 sysdeps/unix/sysv/linux/arm/le/libc.abilist   |   1 +
 sysdeps/unix/sysv/linux/bits/fcntl-linux.h    |  22 ++
 sysdeps/unix/sysv/linux/bits/openat2.h        |  61 +++++
 sysdeps/unix/sysv/linux/csky/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/hppa/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/i386/libc.abilist     |   1 +
 .../sysv/linux/loongarch/lp64/libc.abilist    |   1 +
 .../sysv/linux/m68k/coldfire/libc.abilist     |   1 +
 .../unix/sysv/linux/m68k/m680x0/libc.abilist  |   1 +
 .../sysv/linux/microblaze/be/libc.abilist     |   1 +
 .../sysv/linux/microblaze/le/libc.abilist     |   1 +
 .../sysv/linux/mips/mips32/fpu/libc.abilist   |   1 +
 .../sysv/linux/mips/mips32/nofpu/libc.abilist |   1 +
 .../sysv/linux/mips/mips64/n32/libc.abilist   |   1 +
 .../sysv/linux/mips/mips64/n64/libc.abilist   |   1 +
 sysdeps/unix/sysv/linux/openat2.c             |  29 ++
 sysdeps/unix/sysv/linux/or1k/libc.abilist     |   1 +
 .../linux/powerpc/powerpc32/fpu/libc.abilist  |   1 +
 .../powerpc/powerpc32/nofpu/libc.abilist      |   1 +
 .../linux/powerpc/powerpc64/be/libc.abilist   |   1 +
 .../linux/powerpc/powerpc64/le/libc.abilist   |   1 +
 .../unix/sysv/linux/riscv/rv32/libc.abilist   |   1 +
 .../unix/sysv/linux/riscv/rv64/libc.abilist   |   1 +
 .../unix/sysv/linux/s390/s390-32/libc.abilist |   1 +
 .../unix/sysv/linux/s390/s390-64/libc.abilist |   1 +
 sysdeps/unix/sysv/linux/sh/be/libc.abilist    |   1 +
 sysdeps/unix/sysv/linux/sh/le/libc.abilist    |   1 +
 .../sysv/linux/sparc/sparc32/libc.abilist     |   1 +
 .../sysv/linux/sparc/sparc64/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/tst-openat2-consts.py |  63 +++++
 sysdeps/unix/sysv/linux/tst-openat2.c         | 259 ++++++++++++++++++
 .../unix/sysv/linux/x86_64/64/libc.abilist    |   1 +
 .../unix/sysv/linux/x86_64/x32/libc.abilist   |   1 +
 41 files changed, 587 insertions(+)
 create mode 100644 sysdeps/unix/sysv/linux/bits/openat2.h
 create mode 100644 sysdeps/unix/sysv/linux/openat2.c
 create mode 100755 sysdeps/unix/sysv/linux/tst-openat2-consts.py
 create mode 100644 sysdeps/unix/sysv/linux/tst-openat2.c

Comments

Florian Weimer March 24, 2025, 3:15 p.m. UTC | #1
* Adhemerval Zanella:

> diff --git a/manual/llio.texi b/manual/llio.texi
> index b6bc7f2210..786ac5b451 100644
> --- a/manual/llio.texi
> +++ b/manual/llio.texi

> +@table @code
> +@item RESOLVE_NO_XDEV
> +Disallow traversal of mount points during path resolution (including all
> +bind mounts).
> +@item RESOLVE_NO_MAGICLINKS
> +Disallow all @strong{magic-link} resolution during path resolution. Magic
> +links are simbolic link-like objects that are found in @strong{procfs};

typo: s[y]mbolic

> +for example the @code{/proc/pid/exe}.
> +@item RESOLVE_NO_SYMLINKS
> +Disallow resolution of symbolic links during path resolution.
> +This option implies @code{RESOLVE_NO_MAGICLINKS}.
> +@item RESOLVE_BENEATH
> +Do not permit the path resolution to succeed if any component of the
> +resolution is not a descendant of the directory indicated by @code{dirfd}.
> +@item RESOLVE_IN_ROOT
> +Treat the directory referred to by @code{dirfd} as the root directory
> +while resolving the @code{pathname}.

Maybe clarify how RESOLVE_BENEATH and RESOLVE_IN_ROOT are different?

I'm guessing, but "../" and "/" are ways for referring to the
@var{dirfd} directory with RESOLVE_IN_ROOT, but are errors for with
RESOLVE_BENEATH.

I think you should use @var{pathname} and @var{dirfd} because they refer
to the openat2 argument below.

> +@item RESOLVE_CACHED
> +Make the open operation fail unless all path components are already
> +present in the kernel's lookup cache.
> +@end table
> +
> +@end table
> +
> +For additional information, consult the manual page @manpageurl{openat2,2}.
> +@xref{Linux Kernel}.
> +@end deftp
> +
> +
> +@deftypefun int openat2 (int @var{dirfd}, const char *@var{pathname}, struct open_how @var{how}, size_t @var{size})

Missing * for @var{how}.

> +@standards{Linux, fcntl.h}
> +@safety{@mtsafe{}@assafe{}@acsafe{}}
> +This function is a extension of the @code{openat} and provides a superset of its
> +functionality.  @xref{Descriptor-Relative Access}.
> +
> +The @code{size} define the expected size of @code{how} data structure.
> +It is recommended to initialize unused fields to zero, either using
> +@code{memset}, or using a structure initializer.

Does the kernel promise that there won't be any padding not covered by
fields?  Otherwise applications really should use memset.

> +On failure, @code{openat2} returns @math{-1} and sets @code{errno}.  The
> +following errors are related the way extensibility is handled.

The last sentence appears to apply to E2BIG

> +@table @code
> +@item E2BIG
> +An extension that the kernel does support was specified in @code{how},


@code{*@var{how}}

> +or a larger struct was used with non-zero fields.
> +@item EAGAIN
> +@code{how.resolve} contains either RESOLVE_IN_ROOT or RESOLVE_BENEATH, and

@code{@var{how}->resolve}


> +the kernel could not ensure that @code{".."} component did not escape.  Or
> +@code{RESOLVE_CACHED} was set, and the open operation cannot be performed
> +using only cached information.
> +@item EINVAL
> +And unknown flag or invalid value was used on @code{how}; or @code{mode}

@code{*@var{how}}

@code{@var{how}->mode}

> +is non-zero, but @code{how.flags} does not contain @code{O_CREAT} or

@code{@var{how}->flags}

> +@code{O_TMPFILE}, or @code{size} is smaller than the ones supported

@var{size}

> +by the kernel.
> +@end table
> +
> +It can also return all the errors @code{openat} returns, or other errors
> +due new fields added by the kernel.
> +
> +Similar to @code{openat}, @code{openat2} is a cancellation point.
> +
> +@strong{NB:} Different than other open-like functions, the kernel only
> +provides the LFS variant.  When the sources are translated with
> +@code{_FILE_OFFSET_BITS == 64} this function are not routed a different

typo: this function [is] not routed

> +symbol.
> +@end deftypefun
> +
> +
>  @deftypefn {Obsolete function} int creat (const char *@var{filename}, mode_t @var{mode})
>  @standards{POSIX.1, fcntl.h}
>  @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{@acsfd{}}}

> diff --git a/sysdeps/unix/sysv/linux/tst-openat2.c b/sysdeps/unix/sysv/linux/tst-openat2.c
> new file mode 100644
> index 0000000000..34b0a87ced
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-openat2.c
> @@ -0,0 +1,259 @@
> +/* Linux openat2 tests.
> +   Copyright (C) 2025 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +/* openat2 always return a file descriptor in LFS mode.  */
> +#define _FILE_OFFSET_BITS 64

Should we really define this?  We should check that the wrapper works
for non-LFS builds, too.

If we document the RESOLVE_BENEATH and RESOLVE_IN_ROOT difference, we
should have test case for that.

> +  for (struct struct_test *t = tests; t != array_end (tests); t++)
> +    {
> +      int fd = openat2 (AT_FDCWD, ".", (struct open_how *) &t->arg, t->size);
> +      if (fd == -1 && errno == ENOSYS)
> +	FAIL_UNSUPPORTED ("openat2 is not supported by the kernel");

This should check if any successes have been observed.  We do not want
to flag the test as UNSUPPORTED in case some strange kernel decides to
signal lack of FS support through ENOSYS (as it happen with
copy_file_range).

Thanks,
Florian
Adhemerval Zanella March 26, 2025, 8:21 p.m. UTC | #2
On 24/03/25 12:15, Florian Weimer wrote:
> * Adhemerval Zanella:
> 
>> diff --git a/manual/llio.texi b/manual/llio.texi
>> index b6bc7f2210..786ac5b451 100644
>> --- a/manual/llio.texi
>> +++ b/manual/llio.texi
> 
>> +@table @code
>> +@item RESOLVE_NO_XDEV
>> +Disallow traversal of mount points during path resolution (including all
>> +bind mounts).
>> +@item RESOLVE_NO_MAGICLINKS
>> +Disallow all @strong{magic-link} resolution during path resolution. Magic
>> +links are simbolic link-like objects that are found in @strong{procfs};
> 
> typo: s[y]mbolic

Ack.

> 
>> +for example the @code{/proc/pid/exe}.
>> +@item RESOLVE_NO_SYMLINKS
>> +Disallow resolution of symbolic links during path resolution.
>> +This option implies @code{RESOLVE_NO_MAGICLINKS}.
>> +@item RESOLVE_BENEATH
>> +Do not permit the path resolution to succeed if any component of the
>> +resolution is not a descendant of the directory indicated by @code{dirfd}.
>> +@item RESOLVE_IN_ROOT
>> +Treat the directory referred to by @code{dirfd} as the root directory
>> +while resolving the @code{pathname}.
> 
> Maybe clarify how RESOLVE_BENEATH and RESOLVE_IN_ROOT are different?
> 
> I'm guessing, but "../" and "/" are ways for referring to the
> @var{dirfd} directory with RESOLVE_IN_ROOT, but are errors for with
> RESOLVE_BENEATH.

Yes, the RESOLVE_BENEATH disallows resolution if any component of the
path is not a descendant of @var{dirfd}.  It means that if have a
directory of:

  /tmp/tempdir
  |- test_dir_link -> /tmp
  |- test_dir_link_2 -> test_dir_link
  |- temp_dir_link -> /tmp/tempdir

calling openat2 with a dirfd referring to '/tmp/tempdir' will fail
for 'test_dir_link', 'test_dir_link_2', or 'temp_dir_link'.

I will add some more information on how each flags works.

> 
> I think you should use @var{pathname} and @var{dirfd} because they refer
> to the openat2 argument below.

Ack.

> 
>> +@item RESOLVE_CACHED
>> +Make the open operation fail unless all path components are already
>> +present in the kernel's lookup cache.
>> +@end table
>> +
>> +@end table
>> +
>> +For additional information, consult the manual page @manpageurl{openat2,2}.
>> +@xref{Linux Kernel}.
>> +@end deftp
>> +
>> +
>> +@deftypefun int openat2 (int @var{dirfd}, const char *@var{pathname}, struct open_how @var{how}, size_t @var{size})
> 
> Missing * for @var{how}.

Ack.

> 
>> +@standards{Linux, fcntl.h}
>> +@safety{@mtsafe{}@assafe{}@acsafe{}}
>> +This function is a extension of the @code{openat} and provides a superset of its
>> +functionality.  @xref{Descriptor-Relative Access}.
>> +
>> +The @code{size} define the expected size of @code{how} data structure.
>> +It is recommended to initialize unused fields to zero, either using
>> +@code{memset}, or using a structure initializer.
> 
> Does the kernel promise that there won't be any padding not covered by
> fields?  Otherwise applications really should use memset.

The initial commit (fddb5d430ad9fa91b49b1d34d0202ffe2fa0e179) that added
openat2 does have comment that the current types for open_how member
were chosen to avoid padding:

    open_how does not contain an embedded size field, because it is of
    little benefit (userspace can figure out the kernel open_how size at
    runtime fairly easily without it). It also only contains u64s (even
    though ->mode arguably should be a u16) to avoid having padding fields
    which are never used in the future.

So I take the idea is to indeed avoid padding in the future and allow 
struct initializer (it is also used on kernel selftests).

> 
>> +On failure, @code{openat2} returns @math{-1} and sets @code{errno}.  The
>> +following errors are related the way extensibility is handled.
> 
> The last sentence appears to apply to E2BIG

Ack, I will remove it.

> 
>> +@table @code
>> +@item E2BIG
>> +An extension that the kernel does support was specified in @code{how},
> 
> 
> @code{*@var{how}}

Ack.

> 
>> +or a larger struct was used with non-zero fields.
>> +@item EAGAIN
>> +@code{how.resolve} contains either RESOLVE_IN_ROOT or RESOLVE_BENEATH, and
> 
> @code{@var{how}->resolve}

Ack.

> 
> 
>> +the kernel could not ensure that @code{".."} component did not escape.  Or
>> +@code{RESOLVE_CACHED} was set, and the open operation cannot be performed
>> +using only cached information.
>> +@item EINVAL
>> +And unknown flag or invalid value was used on @code{how}; or @code{mode}
> 
> @code{*@var{how}}
> 
> @code{@var{how}->mode}

Ack.

> 
>> +is non-zero, but @code{how.flags} does not contain @code{O_CREAT} or
> 
> @code{@var{how}->flags}

Ack.

> 
>> +@code{O_TMPFILE}, or @code{size} is smaller than the ones supported
> 
> @var{size}

Ack.

> 
>> +by the kernel.
>> +@end table
>> +
>> +It can also return all the errors @code{openat} returns, or other errors
>> +due new fields added by the kernel.
>> +
>> +Similar to @code{openat}, @code{openat2} is a cancellation point.
>> +
>> +@strong{NB:} Different than other open-like functions, the kernel only
>> +provides the LFS variant.  When the sources are translated with
>> +@code{_FILE_OFFSET_BITS == 64} this function are not routed a different
> 
> typo: this function [is] not routed

Ack.

> 
>> +symbol.
>> +@end deftypefun
>> +
>> +
>>  @deftypefn {Obsolete function} int creat (const char *@var{filename}, mode_t @var{mode})
>>  @standards{POSIX.1, fcntl.h}
>>  @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{@acsfd{}}}
> 
>> diff --git a/sysdeps/unix/sysv/linux/tst-openat2.c b/sysdeps/unix/sysv/linux/tst-openat2.c
>> new file mode 100644
>> index 0000000000..34b0a87ced
>> --- /dev/null
>> +++ b/sysdeps/unix/sysv/linux/tst-openat2.c
>> @@ -0,0 +1,259 @@
>> +/* Linux openat2 tests.
>> +   Copyright (C) 2025 Free Software Foundation, Inc.
>> +   This file is part of the GNU C Library.
>> +
>> +   The GNU C Library is free software; you can redistribute it and/or
>> +   modify it under the terms of the GNU Lesser General Public
>> +   License as published by the Free Software Foundation; either
>> +   version 2.1 of the License, or (at your option) any later version.
>> +
>> +   The GNU C Library is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> +   Lesser General Public License for more details.
>> +
>> +   You should have received a copy of the GNU Lesser General Public
>> +   License along with the GNU C Library; if not, see
>> +   <https://www.gnu.org/licenses/>.  */
>> +
>> +/* openat2 always return a file descriptor in LFS mode.  */
>> +#define _FILE_OFFSET_BITS 64
> 
> Should we really define this?  We should check that the wrapper works
> for non-LFS builds, too.
> 
> If we document the RESOLVE_BENEATH and RESOLVE_IN_ROOT difference, we
> should have test case for that.

Ack.

> 
>> +  for (struct struct_test *t = tests; t != array_end (tests); t++)
>> +    {
>> +      int fd = openat2 (AT_FDCWD, ".", (struct open_how *) &t->arg, t->size);
>> +      if (fd == -1 && errno == ENOSYS)
>> +	FAIL_UNSUPPORTED ("openat2 is not supported by the kernel");
> 
> This should check if any successes have been observed.  We do not want
> to flag the test as UNSUPPORTED in case some strange kernel decides to
> signal lack of FS support through ENOSYS (as it happen with
> copy_file_range).
Fair enough, I will add a early test for openat2 existence.
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index c2a978b460..073c2a945a 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,10 @@  Major new features:
 
 * On Linux, the pthread_gettid_np function has been added.
 
+* On Linux, the openat2 function has been added.  It is an extension of
+  openat and provides a superset of its functionality.  It is supported only
+  in LFS mode and it is a cancellable entrypoint.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
   [Add deprecations, removals and changes affecting compatibility here]
diff --git a/manual/llio.texi b/manual/llio.texi
index b6bc7f2210..786ac5b451 100644
--- a/manual/llio.texi
+++ b/manual/llio.texi
@@ -218,6 +218,106 @@  new, extended API using 64 bit file sizes and offsets transparently
 replaces the old API.
 @end deftypefun
 
+@deftp {Data Type} {struct open_how}
+@standards{Linux, fcntl.h}
+The @code{open_how} structure describes how to open a file using @code{openat2}.
+
+@strong{Portability note:} In the future, additional fields can be added
+to @code{struct open_how} at the end, so that the size of this data
+type changes.  Do not use it in places where this matters, such as
+structure fields in installed header files, where such a change could
+impact the application binary interface (ABI).
+
+The following generic fields are available.
+
+@table @code
+@item flags
+This field specifies the file creation and file status flags to use when
+opening the file.
+All of the @code{O_*} flags defined for @code{openat} are valid.
+Different than @code{openat}, @code{openat2} returns an error for unknown
+or conflicting values.
+
+@item mode
+This field specifies the mode for the new file, similar to @code{mode}
+argument of @code{openat}.  Different than @code{openat}, @code{openat2}
+returns an error for invalid value (like value larger than @code{07777}).
+
+@item resolve
+This is a bitmask of flags that modify the way in @strong{all} components
+of @code{pathname}.  It allows trusted programs to restrict how untrusted
+paths are resolved.  The following generic flags are available.
+
+@table @code
+@item RESOLVE_NO_XDEV
+Disallow traversal of mount points during path resolution (including all
+bind mounts).
+@item RESOLVE_NO_MAGICLINKS
+Disallow all @strong{magic-link} resolution during path resolution. Magic
+links are simbolic link-like objects that are found in @strong{procfs};
+for example the @code{/proc/pid/exe}.
+@item RESOLVE_NO_SYMLINKS
+Disallow resolution of symbolic links during path resolution.
+This option implies @code{RESOLVE_NO_MAGICLINKS}.
+@item RESOLVE_BENEATH
+Do not permit the path resolution to succeed if any component of the
+resolution is not a descendant of the directory indicated by @code{dirfd}.
+@item RESOLVE_IN_ROOT
+Treat the directory referred to by @code{dirfd} as the root directory
+while resolving the @code{pathname}.
+@item RESOLVE_CACHED
+Make the open operation fail unless all path components are already
+present in the kernel's lookup cache.
+@end table
+
+@end table
+
+For additional information, consult the manual page @manpageurl{openat2,2}.
+@xref{Linux Kernel}.
+@end deftp
+
+
+@deftypefun int openat2 (int @var{dirfd}, const char *@var{pathname}, struct open_how @var{how}, size_t @var{size})
+@standards{Linux, fcntl.h}
+@safety{@mtsafe{}@assafe{}@acsafe{}}
+This function is a extension of the @code{openat} and provides a superset of its
+functionality.  @xref{Descriptor-Relative Access}.
+
+The @code{size} define the expected size of @code{how} data structure.
+It is recommended to initialize unused fields to zero, either using
+@code{memset}, or using a structure initializer.
+
+On failure, @code{openat2} returns @math{-1} and sets @code{errno}.  The
+following errors are related the way extensibility is handled.
+
+@table @code
+@item E2BIG
+An extension that the kernel does support was specified in @code{how},
+or a larger struct was used with non-zero fields.
+@item EAGAIN
+@code{how.resolve} contains either RESOLVE_IN_ROOT or RESOLVE_BENEATH, and
+the kernel could not ensure that @code{".."} component did not escape.  Or
+@code{RESOLVE_CACHED} was set, and the open operation cannot be performed
+using only cached information.
+@item EINVAL
+And unknown flag or invalid value was used on @code{how}; or @code{mode}
+is non-zero, but @code{how.flags} does not contain @code{O_CREAT} or
+@code{O_TMPFILE}, or @code{size} is smaller than the ones supported
+by the kernel.
+@end table
+
+It can also return all the errors @code{openat} returns, or other errors
+due new fields added by the kernel.
+
+Similar to @code{openat}, @code{openat2} is a cancellation point.
+
+@strong{NB:} Different than other open-like functions, the kernel only
+provides the LFS variant.  When the sources are translated with
+@code{_FILE_OFFSET_BITS == 64} this function are not routed a different
+symbol.
+@end deftypefun
+
+
 @deftypefn {Obsolete function} int creat (const char *@var{filename}, mode_t @var{mode})
 @standards{POSIX.1, fcntl.h}
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{@acsfd{}}}
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index dcd87b295e..196b954d5e 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -135,6 +135,7 @@  sysdep_headers += \
   bits/mman-linux.h \
   bits/mman-map-flags-generic.h \
   bits/mman-shared.h \
+  bits/openat2.h \
   bits/procfs-extra.h \
   bits/procfs-id.h \
   bits/procfs-prregset.h \
@@ -615,6 +616,7 @@  sysdep_routines += \
   internal_statvfs \
   open64_nocancel \
   open_nocancel \
+  openat2 \
   openat64_nocancel \
   openat_nocancel \
   pread64_nocancel \
@@ -635,7 +637,19 @@  tests += \
   tst-fallocate64 \
   tst-getcwd-smallbuff \
   tst-o_path-locks \
+  tst-openat2 \
   # tests
+
+tests-special += \
+  $(objpfx)tst-openat2-consts.out \
+  # tests-special
+$(objpfx)tst-openat2-consts.out: ../sysdeps/unix/sysv/linux/tst-openat2-consts.py
+	$(sysdeps-linux-python) \
+	  ../sysdeps/unix/sysv/linux/tst-openat2-consts.py \
+	    $(sysdeps-linux-python-cc) \
+	  < /dev/null > $@ 2>&1; $(evaluate-test)
+$(objpfx)tst-openat2-consts.out: $(sysdeps-linux-python-deps)
+
 endif
 
 ifeq ($(subdir),elf)
diff --git a/sysdeps/unix/sysv/linux/Versions b/sysdeps/unix/sysv/linux/Versions
index 55d565545a..f921f954c1 100644
--- a/sysdeps/unix/sysv/linux/Versions
+++ b/sysdeps/unix/sysv/linux/Versions
@@ -332,6 +332,9 @@  libc {
     sched_getattr;
     sched_setattr;
   }
+  GLIBC_2.42 {
+    openat2;
+  }
   GLIBC_PRIVATE {
     # functions used in other libraries
     __syscall_rt_sigqueueinfo;
diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
index afbb38fb0e..b8025fff19 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
@@ -2751,4 +2751,5 @@  GLIBC_2.39 stdc_trailing_zeros_us F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
index ea11409903..bbe891adb9 100644
--- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
+++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
@@ -3098,6 +3098,7 @@  GLIBC_2.4 wscanf F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/arc/libc.abilist b/sysdeps/unix/sysv/linux/arc/libc.abilist
index c6edd66fe4..27d116b1f6 100644
--- a/sysdeps/unix/sysv/linux/arc/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arc/libc.abilist
@@ -2512,4 +2512,5 @@  GLIBC_2.39 stdc_trailing_zeros_us F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/arm/be/libc.abilist b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
index 00e46c2f7f..c2986f9634 100644
--- a/sysdeps/unix/sysv/linux/arm/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
@@ -2804,6 +2804,7 @@  GLIBC_2.4 xprt_unregister F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/arm/le/libc.abilist b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
index 3a87471bfe..cb94c6ef17 100644
--- a/sysdeps/unix/sysv/linux/arm/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
@@ -2801,6 +2801,7 @@  GLIBC_2.4 xprt_unregister F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/bits/fcntl-linux.h b/sysdeps/unix/sysv/linux/bits/fcntl-linux.h
index dfc554aafc..3fab352061 100644
--- a/sysdeps/unix/sysv/linux/bits/fcntl-linux.h
+++ b/sysdeps/unix/sysv/linux/bits/fcntl-linux.h
@@ -453,6 +453,28 @@  extern int name_to_handle_at (int __dfd, const char *__name,
 extern int open_by_handle_at (int __mountdirfd, struct file_handle *__handle,
 			      int __flags);
 
+#ifdef __has_include
+# if __has_include ("linux/openat2.h")
+#  include "linux/openat2.h"
+#  define __glibc_has_open_how 1
+# endif
+#endif
+
+#include <bits/openat2.h>
+
+/* Similar to `openat' but the arguments are packed on HOW with the size
+   USIZE.  If flags and mode from HOW are non-zero, then openat2 operates
+   like openat.
+
+   Unlike openat2, unknown or invalid flags result in an error (EINVAL),
+   rather than being ignored.  The mode must be zero unless one O_CREAT
+   or O_TMPFILE are set.
+
+   The kernel does not support legacy non-LFS interface.  */
+extern int openat2 (int __dfd, const char *__filename, struct open_how *__how,
+		    size_t __usize)
+     __nonnull ((2, 3));
+
 #endif	/* use GNU */
 
 __END_DECLS
diff --git a/sysdeps/unix/sysv/linux/bits/openat2.h b/sysdeps/unix/sysv/linux/bits/openat2.h
new file mode 100644
index 0000000000..6059095bc1
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/bits/openat2.h
@@ -0,0 +1,61 @@ 
+/* openat2 definition.  Linux specific.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _FCNTL_H
+# error "Never use <bits/openat2.h> directly; include <fcntl.h> instead."
+#endif
+
+#ifndef __glibc_has_open_how
+/* Arguments for how openat2 should open the target path.  */
+struct open_how
+{
+  __uint64_t flags;
+  __uint64_t mode;
+  __uint64_t resolve;
+};
+#endif
+
+/* how->resolve flags for openat2. */
+#ifndef RESOLVE_NO_XDEV
+# define RESOLVE_NO_XDEV	0x01 /* Block mount-point crossings
+					(includes bind-mounts).  */
+#endif
+#ifndef RESOLVE_NO_MAGICLINKS
+# define RESOLVE_NO_MAGICLINKS	0x02 /* Block traversal through procfs-style
+					"magic-links".  */
+#endif
+#ifndef RESOLVE_NO_SYMLINKS
+# define RESOLVE_NO_SYMLINKS	0x04 /* Block traversal through all symlinks
+					(implies OEXT_NO_MAGICLINKS).  */
+#endif
+#ifndef RESOLVE_BENEATH
+# define RESOLVE_BENEATH	0x08 /* Block "lexical" trickery like
+					"..", symlinks, and absolute
+					paths which escape the dirfd.  */
+#endif
+#ifndef RESOLVE_IN_ROOT
+# define RESOLVE_IN_ROOT	0x10 /* Make all jumps to "/" and ".."
+					be scoped inside the dirfd
+					(similar to chroot).  */
+#endif
+#ifndef RESOLVE_CACHED
+# define RESOLVE_CACHED		0x20 /* Only complete if resolution can be
+					completed through cached lookup. May
+					return -EAGAIN if that's not
+					possible.  */
+#endif
diff --git a/sysdeps/unix/sysv/linux/csky/libc.abilist b/sysdeps/unix/sysv/linux/csky/libc.abilist
index b819f40f50..4c2cf23899 100644
--- a/sysdeps/unix/sysv/linux/csky/libc.abilist
+++ b/sysdeps/unix/sysv/linux/csky/libc.abilist
@@ -2788,4 +2788,5 @@  GLIBC_2.39 stdc_trailing_zeros_us F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
index 5cb0987348..02691d63b2 100644
--- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
+++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
@@ -2825,6 +2825,7 @@  GLIBC_2.41 cacheflush F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
index 1ec48127e1..62f2ce6652 100644
--- a/sysdeps/unix/sysv/linux/i386/libc.abilist
+++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
@@ -3008,6 +3008,7 @@  GLIBC_2.4 unshare F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
index 82b6b0d196..1984c1aeee 100644
--- a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
@@ -2272,4 +2272,5 @@  GLIBC_2.39 stdc_trailing_zeros_us F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
index 03818c428f..c0e8c68f49 100644
--- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
@@ -2784,6 +2784,7 @@  GLIBC_2.4 xprt_unregister F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
index a2b3d25f48..987322b027 100644
--- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
@@ -2951,6 +2951,7 @@  GLIBC_2.4 unshare F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
index bc00403c50..e252ec1e3d 100644
--- a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
@@ -2837,4 +2837,5 @@  GLIBC_2.39 stdc_trailing_zeros_us F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
index 5606a7027b..3552870fec 100644
--- a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
@@ -2834,4 +2834,5 @@  GLIBC_2.39 stdc_trailing_zeros_us F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
index 5fab619fd6..f688050cda 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
@@ -2912,6 +2912,7 @@  GLIBC_2.4 unshare F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
index 5ba810d096..74444db817 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
@@ -2907,6 +2907,7 @@  GLIBC_2.4 renameat F
 GLIBC_2.4 symlinkat F
 GLIBC_2.4 unlinkat F
 GLIBC_2.4 unshare F
+GLIBC_2.41 openat2 F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
index e1b8f13414..f21f914c5a 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
@@ -2918,6 +2918,7 @@  GLIBC_2.4 unshare F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
index c0ee223f3f..2272eed24d 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
@@ -2820,6 +2820,7 @@  GLIBC_2.4 unshare F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/openat2.c b/sysdeps/unix/sysv/linux/openat2.c
new file mode 100644
index 0000000000..bb4f3e1f16
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/openat2.c
@@ -0,0 +1,29 @@ 
+/* Linux openat2 syscall implementation.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <fcntl.h>
+#include <bits/openat2.h>
+#include <sysdep-cancel.h>
+
+int
+__openat2 (int dfd, const char *filename, struct open_how *how,
+	   size_t usize)
+{
+  return SYSCALL_CANCEL (openat2, dfd, filename, how, usize);
+}
+weak_alias (__openat2, openat2)
diff --git a/sysdeps/unix/sysv/linux/or1k/libc.abilist b/sysdeps/unix/sysv/linux/or1k/libc.abilist
index 227746ae57..4e6a759bf8 100644
--- a/sysdeps/unix/sysv/linux/or1k/libc.abilist
+++ b/sysdeps/unix/sysv/linux/or1k/libc.abilist
@@ -2262,4 +2262,5 @@  GLIBC_2.40 swapcontext F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
index 46fd69dc86..601625c246 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
@@ -3141,6 +3141,7 @@  GLIBC_2.4 wscanf F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
index 9887e117d8..a8150c7b91 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
@@ -3186,6 +3186,7 @@  GLIBC_2.4 wscanf F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
index 2600dc2941..a7b264fb95 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
@@ -2895,6 +2895,7 @@  GLIBC_2.4 wscanf F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
index d803fecff8..06da32a190 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
@@ -2971,4 +2971,5 @@  GLIBC_2.39 stdc_trailing_zeros_us F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
index a2646bde63..81e6791623 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
@@ -2515,4 +2515,5 @@  GLIBC_2.40 __riscv_hwprobe F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
index ad18f29f40..68ad71f3eb 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
@@ -2715,4 +2715,5 @@  GLIBC_2.40 __riscv_hwprobe F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
index 2f76c27fb9..58dbe0104b 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
@@ -3139,6 +3139,7 @@  GLIBC_2.4 wscanf F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
index 4ea336999c..705c150fca 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
@@ -2932,6 +2932,7 @@  GLIBC_2.4 wscanf F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/sh/be/libc.abilist b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
index f245f8f755..6b71d91ffb 100644
--- a/sysdeps/unix/sysv/linux/sh/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
@@ -2831,6 +2831,7 @@  GLIBC_2.4 unshare F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/sh/le/libc.abilist b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
index 4c654a51a3..efbd716ed8 100644
--- a/sysdeps/unix/sysv/linux/sh/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
@@ -2828,6 +2828,7 @@  GLIBC_2.4 unshare F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
index d89a81edcd..ea9347c93c 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
@@ -3160,6 +3160,7 @@  GLIBC_2.4 wscanf F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
index ee7b84249c..67ca148af8 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
@@ -2796,6 +2796,7 @@  GLIBC_2.4 unshare F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/tst-openat2-consts.py b/sysdeps/unix/sysv/linux/tst-openat2-consts.py
new file mode 100755
index 0000000000..9b65d184d7
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-openat2-consts.py
@@ -0,0 +1,63 @@ 
+#!/usr/bin/python3
+# Test that glibc's sys/openat2.h constants match the kernel's.
+# Copyright (C) 2025 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+import argparse
+import sys
+
+import glibcextract
+import glibcsyscalls
+
+
+def main():
+    """The main entry point."""
+    parser = argparse.ArgumentParser(
+        description="Test that glibc's sys/openat2.h constants "
+        "match the kernel's.")
+    parser.add_argument('--cc', metavar='CC',
+                        help='C compiler (including options) to use')
+    args = parser.parse_args()
+
+    if glibcextract.compile_c_snippet(
+            '#include <linux/openat2.h>',
+            args.cc).returncode != 0:
+        sys.exit (77)
+
+    linux_version_headers = glibcsyscalls.linux_kernel_version(args.cc)
+    # Constants in glibc were updated to match Linux v6.8.  When glibc
+    # constants are updated this value should be updated to match the
+    # released kernel version from which the constants were taken.
+    linux_version_glibc = (6, 8)
+    def check(cte, exclude=None):
+        return glibcextract.compare_macro_consts(
+                '#define _FCNTL_H\n'
+                '#include <stdint.h>\n'
+                '#include <bits/openat2.h>\n',
+                '#include <asm/fcntl.h>\n'
+                '#include <linux/openat2.h>\n',
+                args.cc,
+                cte,
+                exclude,
+                linux_version_glibc > linux_version_headers,
+                linux_version_headers > linux_version_glibc)
+
+    status = check('RESOLVE.*')
+    sys.exit(status)
+
+if __name__ == '__main__':
+    main()
diff --git a/sysdeps/unix/sysv/linux/tst-openat2.c b/sysdeps/unix/sysv/linux/tst-openat2.c
new file mode 100644
index 0000000000..34b0a87ced
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-openat2.c
@@ -0,0 +1,259 @@ 
+/* Linux openat2 tests.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* openat2 always return a file descriptor in LFS mode.  */
+#define _FILE_OFFSET_BITS 64
+
+#include <array_length.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xunistd.h>
+
+static int dir_fd;
+
+static void
+do_prepare (int argc, char *argv[])
+{
+  char *temp_dir = support_create_temp_directory ("tst-openat2");
+  dir_fd = xopen (temp_dir, O_RDONLY | O_DIRECTORY, 0);
+}
+#define PREPARE do_prepare
+
+static int
+do_test_struct (void)
+{
+  static struct struct_test
+  {
+    struct open_how_ext
+    {
+      struct open_how inner;
+      int extra1;
+      int extra2;
+      int extra3;
+    } arg;
+    size_t size;
+    int err;
+  } tests[] =
+  {
+    {
+      /* Zero size.  */
+      .arg.inner.flags = O_RDONLY,
+      .size = 0,
+      .err = EINVAL,
+    },
+    {
+      /* Normal struct.  */
+      .arg.inner.flags = O_RDONLY,
+      .size = sizeof (struct open_how),
+    },
+    {
+      /* Larger struct, zeroed out the unused values.  */
+      .arg.inner.flags = O_RDONLY,
+      .size = sizeof (struct open_how_ext),
+    },
+    {
+      /* Larger struct, non-zeroed out the unused values.  */
+      .arg.inner.flags = O_RDONLY,
+      .arg.extra1 = 0xdeadbeef,
+      .size = sizeof (struct open_how_ext),
+      .err = E2BIG,
+    },
+    {
+      /* Larger struct, non-zeroed out the unused values.  */
+      .arg.inner.flags = O_RDONLY,
+      .arg.extra2 = 0xdeadbeef,
+      .size = sizeof (struct open_how_ext),
+      .err = E2BIG,
+    },
+  };
+
+  for (struct struct_test *t = tests; t != array_end (tests); t++)
+    {
+      int fd = openat2 (AT_FDCWD, ".", (struct open_how *) &t->arg, t->size);
+      if (fd == -1 && errno == ENOSYS)
+	FAIL_UNSUPPORTED ("openat2 is not supported by the kernel");
+
+      if (t->err != 0)
+	{
+	  TEST_COMPARE (fd, -1);
+	  TEST_COMPARE (errno, t->err);
+	}
+      else
+	TEST_VERIFY (fd >= 0);
+    }
+
+  return 0;
+}
+
+static int
+do_test_flags (void)
+{
+  static struct flag_test
+  {
+    struct open_how how;
+    int err;
+  } tests[] =
+  {
+    /* O_TMPFILE is incompatible with O_PATH and O_CREAT.  */
+    { .how.flags = O_TMPFILE | O_PATH | O_RDWR, .err = EINVAL },
+    { .how.flags = O_TMPFILE | O_CREAT | O_RDWR, .err = EINVAL },
+
+    /* O_PATH only permits certain other flags to be set ...  */
+    { .how.flags = O_PATH | O_CLOEXEC },
+    { .how.flags = O_PATH | O_DIRECTORY },
+    { .how.flags = O_PATH | O_NOFOLLOW },
+    /* ... and others are absolutely not permitted. */
+    { .how.flags = O_PATH | O_RDWR, .err = EINVAL },
+    { .how.flags = O_PATH | O_CREAT, .err = EINVAL },
+    { .how.flags = O_PATH | O_EXCL, .err = EINVAL },
+    { .how.flags = O_PATH | O_NOCTTY, .err = EINVAL },
+    { .how.flags = O_PATH | O_DIRECT, .err = EINVAL },
+
+    /* ->mode must only be set with O_{CREAT,TMPFILE}. */
+    { .how.flags = O_RDONLY, .how.mode = 0600, .err = EINVAL },
+    { .how.flags = O_PATH,   .how.mode = 0600, .err = EINVAL },
+    { .how.flags = O_CREAT,  .how.mode = 0600 },
+    { .how.flags = O_TMPFILE | O_RDWR, .how.mode = 0600 },
+    /* ->mode must only contain 0777 bits. */
+    { .how.flags = O_CREAT, .how.mode = 0xFFFF, .err = EINVAL },
+    { .how.flags = O_CREAT, .how.mode = 0xC000000000000000ULL, .err = EINVAL },
+    { .how.flags = O_TMPFILE | O_RDWR, .how.mode = 0x1337, .err = EINVAL },
+    { .how.flags = O_TMPFILE | O_RDWR, .how.mode = 0x0000A00000000000ULL,
+      .err = EINVAL },
+
+    /* ->resolve flags must not conflict. */
+    { .how.flags = O_RDONLY,
+      .how.resolve = RESOLVE_BENEATH | RESOLVE_IN_ROOT,
+      .err = EINVAL },
+
+    /* ->resolve must only contain RESOLVE_* flags.  */
+    { .how.flags = O_RDONLY,
+      .how.resolve = 0x1337, .err = EINVAL },
+    { .how.flags = O_CREAT,
+      .how.resolve = 0x1337, .err = EINVAL },
+    { .how.flags = O_TMPFILE | O_RDWR,
+      .how.resolve = 0x1337, .err = EINVAL },
+    { .how.flags = O_PATH,
+      .how.resolve = 0x1337, .err = EINVAL },
+
+    /* currently unknown upper 32 bit rejected.  */
+    { .how.flags = O_RDONLY | (1ULL << 63),
+      .how.resolve = 0, .err = EINVAL },
+  };
+
+  for (struct flag_test *t = tests; t != array_end (tests); t++)
+    {
+      char *path;
+      if (t->how.flags & O_CREAT)
+	{
+	  int temp_fd = create_temp_file ("tst-openat2.", &path);
+	  TEST_VERIFY_EXIT (temp_fd != -1);
+	  xunlink (path);
+	}
+      else
+	path = (char *) ".";
+
+      int fd = openat2 (AT_FDCWD, path, &t->how, sizeof (struct open_how));
+      if (fd != 0 && errno == EOPNOTSUPP)
+	{
+	  /* Skip the testcase if FS does not support the operation (e.g.
+	     valid O_TMPFILE on NFS).  */
+	  continue;
+	}
+
+      if (t->err != 0)
+	{
+	  TEST_COMPARE (fd, -1);
+	  TEST_COMPARE (errno, t->err);
+	}
+      else
+	TEST_VERIFY (fd >= 0);
+    }
+
+  return 0;
+}
+
+static int
+do_test_basic (void)
+{
+  int fd;
+
+  fd = openat2 (dir_fd,
+		"some-file",
+		&(struct open_how)
+		{
+		  .flags = O_CREAT|O_RDWR|O_EXCL,
+		  .mode = 0666,
+		},
+		sizeof (struct open_how));
+  TEST_VERIFY (fd != -1);
+
+  xwrite (fd, "hello", 5);
+
+  /* Before closing the file, try using this file descriptor to open
+     another file.  This must fail.  */
+  {
+    int fd2 = openat2 (fd,
+		       "should-not-work",
+		       &(struct open_how)
+		       {
+			 .flags = O_CREAT|O_RDWR|O_EXCL,
+			 .mode = 0666,
+		       },
+		       sizeof (struct open_how));
+    TEST_COMPARE (fd2, -1);
+    TEST_COMPARE (errno, ENOTDIR);
+  }
+
+  /* Remove the created file.  */
+  int cwdfd = xopen (".", O_RDONLY | O_DIRECTORY, 0);
+  TEST_COMPARE (fchdir (dir_fd), 0);
+  xunlink ("some-file");
+  TEST_COMPARE (fchdir (cwdfd), 0);
+
+  xclose (dir_fd);
+  xclose (cwdfd);
+
+  fd = openat2 (dir_fd,
+		"some-file",
+		&(struct open_how)
+		{
+		  .flags = O_CREAT|O_RDWR|O_EXCL,
+		  .mode = 0666,
+		},
+		sizeof (struct open_how));
+  TEST_COMPARE (fd, -1);
+  TEST_COMPARE (errno, EBADF);
+
+  return 0;
+}
+
+static int
+do_test (void)
+{
+  do_test_struct ();
+  do_test_flags ();
+  do_test_basic ();
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
index 00155d9f3e..640c4f838d 100644
--- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
@@ -2747,6 +2747,7 @@  GLIBC_2.4 unshare F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F
 GLIBC_2.5 __readlinkat_chk F
 GLIBC_2.5 inet6_opt_append F
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
index 18765ec3b8..f309892e5d 100644
--- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
@@ -2766,4 +2766,5 @@  GLIBC_2.39 stdc_trailing_zeros_us F
 GLIBC_2.41 sched_getattr F
 GLIBC_2.41 sched_setattr F
 GLIBC_2.42 __inet_ntop_chk F
+GLIBC_2.42 openat2 F
 GLIBC_2.42 pthread_gettid_np F