[RFC,3/5] elf: Add support to memory sealing

Message ID 20240611153220.165430-4-adhemerval.zanella@linaro.org
State New
Headers
Series Add support for memory sealing |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm fail Test failed

Commit Message

Adhemerval Zanella June 11, 2024, 3:27 p.m. UTC
  The new Linux mseal syscall allows seal memory mappings to avoid
further changes such as memory protection or remap.  The sealing
is done in multiple places where the memory is supposed to
be immutable over program execution:

  * All shared library dependencies from the binary, including the
    read-only segments after PT_GNU_RELRO setup.

  * The binary itself, including dynamic and static links.  In both
    It is up either to binary or the loader to set up the sealing.

  * The vDSO vma provided by the kernel (if existent).

  * Any preload libraries.

  * Any library loaded with dlopen with RTLD_NODELETE flag.

For binary dependencies, the RTLD_NODELETE signals the
link_map should be sealed.  It also makes dlopen objects with the
flag sealed as well.

The sealing is controlled by a new tunable, glibc.rtld.seal, with
three different states:

  0. Disabled where no sealing is done.  This is the default.

  1. Enabled, where the loader will issue the mseal syscall on the
     memory mappings but any failure will be ignored.  This is
     the default.

  2. Enforce, similar to Enabled but any failure from the mseal
     will terminate the process.

Checked on x86_64-linux-gnu and aarch64-linux-gnu.
---
 elf/dl-load.c                                 |   2 +
 elf/dl-mseal-mode.h                           |  29 ++
 elf/dl-open.c                                 |   4 +
 elf/dl-reloc.c                                |  49 ++++
 elf/dl-support.c                              |   7 +
 elf/dl-tunables.list                          |   6 +
 elf/rtld.c                                    |  10 +-
 elf/setup-vdso.h                              |   3 +
 elf/tst-rtld-list-tunables.exp                |   1 +
 include/link.h                                |   6 +
 manual/tunables.texi                          |  35 +++
 string/strerrorname_np.c                      |   1 +
 sysdeps/generic/dl-mseal.h                    |  25 ++
 sysdeps/generic/ldsodefs.h                    |   6 +
 sysdeps/unix/sysv/linux/Makefile              |  45 +++
 sysdeps/unix/sysv/linux/dl-mseal.c            |  51 ++++
 sysdeps/unix/sysv/linux/dl-mseal.h            |  29 ++
 sysdeps/unix/sysv/linux/lib-tst-dl_mseal-1.c  |  19 ++
 sysdeps/unix/sysv/linux/lib-tst-dl_mseal-2.c  |  19 ++
 .../sysv/linux/lib-tst-dl_mseal-dlopen-1-1.c  |  19 ++
 .../sysv/linux/lib-tst-dl_mseal-dlopen-1.c    |  19 ++
 .../sysv/linux/lib-tst-dl_mseal-dlopen-2-1.c  |  19 ++
 .../sysv/linux/lib-tst-dl_mseal-dlopen-2.c    |  19 ++
 sysdeps/unix/sysv/linux/tst-dl_mseal-static.c |   2 +
 sysdeps/unix/sysv/linux/tst-dl_mseal.c        | 267 ++++++++++++++++++
 25 files changed, 689 insertions(+), 3 deletions(-)
 create mode 100644 elf/dl-mseal-mode.h
 create mode 100644 sysdeps/generic/dl-mseal.h
 create mode 100644 sysdeps/unix/sysv/linux/dl-mseal.c
 create mode 100644 sysdeps/unix/sysv/linux/dl-mseal.h
 create mode 100644 sysdeps/unix/sysv/linux/lib-tst-dl_mseal-1.c
 create mode 100644 sysdeps/unix/sysv/linux/lib-tst-dl_mseal-2.c
 create mode 100644 sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-1-1.c
 create mode 100644 sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-1.c
 create mode 100644 sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-2-1.c
 create mode 100644 sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-2.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-dl_mseal-static.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-dl_mseal.c
  

Comments

Jonathan Corbet June 11, 2024, 8:47 p.m. UTC | #1
Adhemerval Zanella <adhemerval.zanella@linaro.org> writes:

> The sealing is controlled by a new tunable, glibc.rtld.seal, with
> three different states:
>
>   0. Disabled where no sealing is done.  This is the default.
>
>   1. Enabled, where the loader will issue the mseal syscall on the
>      memory mappings but any failure will be ignored.  This is
>      the default.
>
>   2. Enforce, similar to Enabled but any failure from the mseal
>      will terminate the process.

Cool - two defaults! :)

It *looks* like the actual default is 0?

Thanks,

jon
  
Adhemerval Zanella June 11, 2024, 9:03 p.m. UTC | #2
> On 11 Jun 2024, at 17:47, Jonathan Corbet <corbet@lwn.net> wrote:
> Adhemerval Zanella <adhemerval.zanella@linaro.org> writes:
> 
>> The sealing is controlled by a new tunable, glibc.rtld.seal, with
>> three different states:
>> 
>>  0. Disabled where no sealing is done.  This is the default.
>> 
>>  1. Enabled, where the loader will issue the mseal syscall on the
>>     memory mappings but any failure will be ignored.  This is
>>     the default.
>> 
>>  2. Enforce, similar to Enabled but any failure from the mseal
>>     will terminate the process.
> 
> Cool - two defaults! :)
> 
> It *looks* like the actual default is 0?
> 
> Thanks,
> 
> jon

Oops, the actual default is 1 (apply, not enforce). I will fix the commit message.
  
Mike Hommey June 21, 2024, 5:09 a.m. UTC | #3
On Tue, Jun 11, 2024 at 12:27:06PM -0300, Adhemerval Zanella wrote:
> The new Linux mseal syscall allows seal memory mappings to avoid
> further changes such as memory protection or remap.  The sealing
> is done in multiple places where the memory is supposed to
> be immutable over program execution:
> 
>   * All shared library dependencies from the binary, including the
>     read-only segments after PT_GNU_RELRO setup.

For what it's worth, this will break current Firefox binaries from
mozilla.org.

Why? Long story short, they are linked with both -Wl,-z,pack-relative-relocs
and -Wl,-z,relro, but because they need to run on old and new systems,
and because the first glibc insists that a binary using RELR relocations
_has_ to have a dependency on the GLIBC_ABI_DT_RELR symbol version,
which is not backwards compatible with older glibcs, the Firefox
binaries are edited to change the DT_RELR tags to something else,
and they contain an init function that applies the relocations instead
of ld.so. That code also temporarily undoes the RELRO madvise to be
able to apply those relocations, and redoes it afterwards.

mseal would prevent that temporary undoing from working and make Firefox
crash on startup.

Had the GLIBC_ABI_DT_RELR symbol version not been a hard requirement, we
wouldn't have ended up in this situation, but here we are.

I'm not sure what the best way to handle the situation would be.
Obviously, there are hackish ways to handle the situation, like removing
the PT_GNU_RELRO and applying it and the mseal manually.

One question is, should a binary be able to opt out of the seal for
whatever reason, individually? (rather than the global tunable, which a
binary can't opt-into on its own, too, although in Firefox's case,
there's a wrapper binary that could set GLIBC_TUNABLES...).

Sorry for the rambling. All the gory details on https://glandium.org/blog/?p=4297.

Mike
  
Adhemerval Zanella June 25, 2024, 9:07 p.m. UTC | #4
On 21/06/24 02:09, Mike Hommey wrote:
> On Tue, Jun 11, 2024 at 12:27:06PM -0300, Adhemerval Zanella wrote:
>> The new Linux mseal syscall allows seal memory mappings to avoid
>> further changes such as memory protection or remap.  The sealing
>> is done in multiple places where the memory is supposed to
>> be immutable over program execution:
>>
>>   * All shared library dependencies from the binary, including the
>>     read-only segments after PT_GNU_RELRO setup.
> 
> For what it's worth, this will break current Firefox binaries from
> mozilla.org.
> 
> Why? Long story short, they are linked with both -Wl,-z,pack-relative-relocs
> and -Wl,-z,relro, but because they need to run on old and new systems,
> and because the first glibc insists that a binary using RELR relocations
> _has_ to have a dependency on the GLIBC_ABI_DT_RELR symbol version,
> which is not backwards compatible with older glibcs, the Firefox
> binaries are edited to change the DT_RELR tags to something else,
> and they contain an init function that applies the relocations instead
> of ld.so. That code also temporarily undoes the RELRO madvise to be
> able to apply those relocations, and redoes it afterwards.
> 
> mseal would prevent that temporary undoing from working and make Firefox
> crash on startup.
> 
> Had the GLIBC_ABI_DT_RELR symbol version not been a hard requirement, we
> wouldn't have ended up in this situation, but here we are.

There was a recent question about the the need of GLIBC_ABI_DT_RELR on
libc-help maillist [1] and the main reason, as Florian has said, is to
avoid hard to debug crashes with binaries/libraries built with DT_RELR
on glibc versions that do not support DT_RELR.

> 
> I'm not sure what the best way to handle the situation would be.
> Obviously, there are hackish ways to handle the situation, like removing
> the PT_GNU_RELRO and applying it and the mseal manually.

Well that's the price you pay by bypassing the loader without a proper
interface to do so, the result will most likely be brittle.  At least with
this RFC you can always disable sealing and apply it yourself after the
_init hack described in the post.

> 
> One question is, should a binary be able to opt out of the seal for
> whatever reason, individually? (rather than the global tunable, which a
> binary can't opt-into on its own, too, although in Firefox's case,
> there's a wrapper binary that could set GLIBC_TUNABLES...).

Currently there is not ELF marking like GNU_PROPERTY_AARCH64_FEATURE_1_AND
for aarch64 BTI, although I won't oppose adding something similar.  However,
I still think we should still apply sealing as default, and make the
sealing flag as opt-out (so we will have hardening as default).  And I think
if it is the idea, to also add its support on same version that sealing was
added to avoid the same issue you trying to hack with RELR.

There is also some discussion on adding system-wide tunables, and my plan
would to allow to add binary specific rules (DJ is working on this).

What I *really* do not want is to add interface sto operate on sealing/hardening
for link_maps (to for instance, disable sealing and apply it after main with
a libc function). These tend to become quite complex and bleed out implementation
details, and, as you have put in the blog post, obsolete over time.

> 
> Sorry for the rambling. All the gory details on https://glandium.org/blog/?p=4297.


[1] https://sourceware.org/pipermail/libc-help/2024-June/006701.html
  
Mike Hommey June 25, 2024, 11:18 p.m. UTC | #5
Thanks for your answer. Just an additional comment below.

On Tue, Jun 25, 2024 at 06:07:23PM -0300, Adhemerval Zanella Netto wrote:
> There was a recent question about the the need of GLIBC_ABI_DT_RELR on
> libc-help maillist [1] and the main reason, as Florian has said, is to
> avoid hard to debug crashes with binaries/libraries built with DT_RELR
> on glibc versions that do not support DT_RELR.

That's a valid reason for the linker to add the version dependency, not
for ld.so to enforce it. But it's too late to relax it anyways.

Mike
  
Adhemerval Zanella June 26, 2024, 11:58 a.m. UTC | #6
On 25/06/24 20:18, Mike Hommey wrote:
> Thanks for your answer. Just an additional comment below.
> 
> On Tue, Jun 25, 2024 at 06:07:23PM -0300, Adhemerval Zanella Netto wrote:
>> There was a recent question about the the need of GLIBC_ABI_DT_RELR on
>> libc-help maillist [1] and the main reason, as Florian has said, is to
>> avoid hard to debug crashes with binaries/libraries built with DT_RELR
>> on glibc versions that do not support DT_RELR.
> 
> That's a valid reason for the linker to add the version dependency, not
> for ld.so to enforce it. But it's too late to relax it anyways.

Old glibc will just ignore the DT_RELR, meaning that relocation won't apply
and the binary will misbehave after loading time.  We are moving away of
such failures mode because such issues are really hard to debug, you have 
to know both dynamic linker and ELF internals to understand why your library 
that you built with a recent binutils works on a recent glibc but fails on
an older one.

Enforcing it is the main reason to add the version dependency.
  
Mike Hommey June 26, 2024, 7:58 p.m. UTC | #7
On Wed, Jun 26, 2024 at 08:58:30AM -0300, Adhemerval Zanella Netto wrote:
> 
> 
> On 25/06/24 20:18, Mike Hommey wrote:
> > Thanks for your answer. Just an additional comment below.
> > 
> > On Tue, Jun 25, 2024 at 06:07:23PM -0300, Adhemerval Zanella Netto wrote:
> >> There was a recent question about the the need of GLIBC_ABI_DT_RELR on
> >> libc-help maillist [1] and the main reason, as Florian has said, is to
> >> avoid hard to debug crashes with binaries/libraries built with DT_RELR
> >> on glibc versions that do not support DT_RELR.
> > 
> > That's a valid reason for the linker to add the version dependency, not
> > for ld.so to enforce it. But it's too late to relax it anyways.
> 
> Old glibc will just ignore the DT_RELR, meaning that relocation won't apply
> and the binary will misbehave after loading time.  We are moving away of
> such failures mode because such issues are really hard to debug, you have 
> to know both dynamic linker and ELF internals to understand why your library 
> that you built with a recent binutils works on a recent glibc but fails on
> an older one.

The point is, if you built your binary with a recent binutils, it
already won't run on an old glibc because of the version dependency.
That will happen whether or not the newer glibc barks when the version
dependency is not there.

Mike
  
Adhemerval Zanella June 26, 2024, 9:20 p.m. UTC | #8
On 26/06/24 16:58, Mike Hommey wrote:
> On Wed, Jun 26, 2024 at 08:58:30AM -0300, Adhemerval Zanella Netto wrote:
>>
>>
>> On 25/06/24 20:18, Mike Hommey wrote:
>>> Thanks for your answer. Just an additional comment below.
>>>
>>> On Tue, Jun 25, 2024 at 06:07:23PM -0300, Adhemerval Zanella Netto wrote:
>>>> There was a recent question about the the need of GLIBC_ABI_DT_RELR on
>>>> libc-help maillist [1] and the main reason, as Florian has said, is to
>>>> avoid hard to debug crashes with binaries/libraries built with DT_RELR
>>>> on glibc versions that do not support DT_RELR.
>>>
>>> That's a valid reason for the linker to add the version dependency, not
>>> for ld.so to enforce it. But it's too late to relax it anyways.
>>
>> Old glibc will just ignore the DT_RELR, meaning that relocation won't apply
>> and the binary will misbehave after loading time.  We are moving away of
>> such failures mode because such issues are really hard to debug, you have 
>> to know both dynamic linker and ELF internals to understand why your library 
>> that you built with a recent binutils works on a recent glibc but fails on
>> an older one.
> 
> The point is, if you built your binary with a recent binutils, it
> already won't run on an old glibc because of the version dependency.
> That will happen whether or not the newer glibc barks when the version
> dependency is not there.

The version dependency is only added if you explicitly use DT_RELR,
which is an optional feature, otherwise the version tags will be ones
from the glibc version is building against.  Different PT_GNU_RELRO, which 
is also optional and added a opt-in hardening, specifying DT_RELR and r
unning on old loader will cause runtime inconsistency.
  
Mike Hommey June 26, 2024, 9:39 p.m. UTC | #9
On Wed, Jun 26, 2024 at 06:20:22PM -0300, Adhemerval Zanella Netto wrote:
> 
> 
> On 26/06/24 16:58, Mike Hommey wrote:
> > On Wed, Jun 26, 2024 at 08:58:30AM -0300, Adhemerval Zanella Netto wrote:
> >>
> >>
> >> On 25/06/24 20:18, Mike Hommey wrote:
> >>> Thanks for your answer. Just an additional comment below.
> >>>
> >>> On Tue, Jun 25, 2024 at 06:07:23PM -0300, Adhemerval Zanella Netto wrote:
> >>>> There was a recent question about the the need of GLIBC_ABI_DT_RELR on
> >>>> libc-help maillist [1] and the main reason, as Florian has said, is to
> >>>> avoid hard to debug crashes with binaries/libraries built with DT_RELR
> >>>> on glibc versions that do not support DT_RELR.
> >>>
> >>> That's a valid reason for the linker to add the version dependency, not
> >>> for ld.so to enforce it. But it's too late to relax it anyways.
> >>
> >> Old glibc will just ignore the DT_RELR, meaning that relocation won't apply
> >> and the binary will misbehave after loading time.  We are moving away of
> >> such failures mode because such issues are really hard to debug, you have 
> >> to know both dynamic linker and ELF internals to understand why your library 
> >> that you built with a recent binutils works on a recent glibc but fails on
> >> an older one.
> > 
> > The point is, if you built your binary with a recent binutils, it
> > already won't run on an old glibc because of the version dependency.
> > That will happen whether or not the newer glibc barks when the version
> > dependency is not there.
> 
> The version dependency is only added if you explicitly use DT_RELR,
> which is an optional feature, otherwise the version tags will be ones
> from the glibc version is building against.  Different PT_GNU_RELRO, which 
> is also optional and added a opt-in hardening, specifying DT_RELR and r
> unning on old loader will cause runtime inconsistency. 

I'm not talking about PT_GNU_RELRO here. My point is, if you build your
binary with a recent binutils and opt-in to DT_RELR, the linker is going
to add the GLIBC_ABI_DT_RELR dependency. If you have a binary using
DT_RELR without a GLIBC_ABI_DT_RELR dependency, it was not produced by a
recent binutils. Currently, the only way to end up in a situation where
you don't have the version depenendency is to either use an old lld
flag, or edit ELF headers manually, and in both cases, you'd be asking
for it. The ld.so check is unnecessary.

But as I said already, it's already too late, it doesn't matter anymore.
  
Adhemerval Zanella June 26, 2024, 9:56 p.m. UTC | #10
On 26/06/24 18:39, Mike Hommey wrote:
> On Wed, Jun 26, 2024 at 06:20:22PM -0300, Adhemerval Zanella Netto wrote:
>>
>>
>> On 26/06/24 16:58, Mike Hommey wrote:
>>> On Wed, Jun 26, 2024 at 08:58:30AM -0300, Adhemerval Zanella Netto wrote:
>>>>
>>>>
>>>> On 25/06/24 20:18, Mike Hommey wrote:
>>>>> Thanks for your answer. Just an additional comment below.
>>>>>
>>>>> On Tue, Jun 25, 2024 at 06:07:23PM -0300, Adhemerval Zanella Netto wrote:
>>>>>> There was a recent question about the the need of GLIBC_ABI_DT_RELR on
>>>>>> libc-help maillist [1] and the main reason, as Florian has said, is to
>>>>>> avoid hard to debug crashes with binaries/libraries built with DT_RELR
>>>>>> on glibc versions that do not support DT_RELR.
>>>>>
>>>>> That's a valid reason for the linker to add the version dependency, not
>>>>> for ld.so to enforce it. But it's too late to relax it anyways.
>>>>
>>>> Old glibc will just ignore the DT_RELR, meaning that relocation won't apply
>>>> and the binary will misbehave after loading time.  We are moving away of
>>>> such failures mode because such issues are really hard to debug, you have 
>>>> to know both dynamic linker and ELF internals to understand why your library 
>>>> that you built with a recent binutils works on a recent glibc but fails on
>>>> an older one.
>>>
>>> The point is, if you built your binary with a recent binutils, it
>>> already won't run on an old glibc because of the version dependency.
>>> That will happen whether or not the newer glibc barks when the version
>>> dependency is not there.
>>
>> The version dependency is only added if you explicitly use DT_RELR,
>> which is an optional feature, otherwise the version tags will be ones
>> from the glibc version is building against.  Different PT_GNU_RELRO, which 
>> is also optional and added a opt-in hardening, specifying DT_RELR and r
>> unning on old loader will cause runtime inconsistency. 
> 
> I'm not talking about PT_GNU_RELRO here. My point is, if you build your
> binary with a recent binutils and opt-in to DT_RELR, the linker is going
> to add the GLIBC_ABI_DT_RELR dependency. If you have a binary using
> DT_RELR without a GLIBC_ABI_DT_RELR dependency, it was not produced by a
> recent binutils. Currently, the only way to end up in a situation where
> you don't have the version depenendency is to either use an old lld
> flag, or edit ELF headers manually, and in both cases, you'd be asking
> for it. The ld.so check is unnecessary.
> 
> But as I said already, it's already too late, it doesn't matter anymore.

But DT_RELR was added on glibc before binutils support with the precondition
that GLIBC_ABI_DT_RELR should be added, as per contract.  H.J implemented 
it on x86 Fangrui adapted it on lld side.

A binary with DT_RELR without GLIBC_ABI_DT_RELR was never supported, you
could have created with some lld version *before* supported was added on
glibc.

The GLIBC_ABI_DT_RELR dependency was added exactly to avoid the very hack
you are trying to do: building a binary that might eventually trigger
runtime inconsistency depending of the glibc version you ran it.
  
Mike Hommey June 27, 2024, 11 p.m. UTC | #11
On Fri, Jun 21, 2024 at 02:09:04PM +0900, Mike Hommey wrote:
> On Tue, Jun 11, 2024 at 12:27:06PM -0300, Adhemerval Zanella wrote:
> > The new Linux mseal syscall allows seal memory mappings to avoid
> > further changes such as memory protection or remap.  The sealing
> > is done in multiple places where the memory is supposed to
> > be immutable over program execution:
> > 
> >   * All shared library dependencies from the binary, including the
> >     read-only segments after PT_GNU_RELRO setup.
> 
> For what it's worth, this will break current Firefox binaries from
> mozilla.org.
> 
> Why? Long story short, they are linked with both -Wl,-z,pack-relative-relocs
> and -Wl,-z,relro, but because they need to run on old and new systems,
> and because the first glibc insists that a binary using RELR relocations
> _has_ to have a dependency on the GLIBC_ABI_DT_RELR symbol version,
> which is not backwards compatible with older glibcs, the Firefox
> binaries are edited to change the DT_RELR tags to something else,
> and they contain an init function that applies the relocations instead
> of ld.so. That code also temporarily undoes the RELRO madvise to be
> able to apply those relocations, and redoes it afterwards.
> 
> mseal would prevent that temporary undoing from working and make Firefox
> crash on startup.
> 
> Had the GLIBC_ABI_DT_RELR symbol version not been a hard requirement, we
> wouldn't have ended up in this situation, but here we are.
> 
> I'm not sure what the best way to handle the situation would be.
> Obviously, there are hackish ways to handle the situation, like removing
> the PT_GNU_RELRO and applying it and the mseal manually.

I just realized this can't work. For some reason I had the impression
the mseal was applied to the RELRO segment, but it's over the entire
library, which makes sense, in hindsight. The problem is that if we
remove RELRO, then... we can't even reapply it afterwards because of the
mseal, leaving us with a writable data section.
But if we disable mseal, we only get to disable it for everything, not
only our libs! (and only if we re-exec with GLIBC_TUNABLES set?)

Mike
  
Florian Weimer June 28, 2024, 5:51 a.m. UTC | #12
* Mike Hommey:

> I just realized this can't work. For some reason I had the impression
> the mseal was applied to the RELRO segment, but it's over the entire
> library, which makes sense, in hindsight. The problem is that if we
> remove RELRO, then... we can't even reapply it afterwards because of the
> mseal, leaving us with a writable data section.
> But if we disable mseal, we only get to disable it for everything, not
> only our libs! (and only if we re-exec with GLIBC_TUNABLES set?)

We can introduce a flag in a dynamic tag at the same time we implement
mseal.  The flag would isntruct the dynamic linker to skip mseal.  It's
going to be some time until link editors know about the flag, but that
doesn't matter in your case because you have a custom linker anyway,
more or less.

Thanks,
Florian
  
Mike Hommey June 28, 2024, 5:58 a.m. UTC | #13
On Fri, Jun 28, 2024 at 07:51:05AM +0200, Florian Weimer wrote:
> * Mike Hommey:
> 
> > I just realized this can't work. For some reason I had the impression
> > the mseal was applied to the RELRO segment, but it's over the entire
> > library, which makes sense, in hindsight. The problem is that if we
> > remove RELRO, then... we can't even reapply it afterwards because of the
> > mseal, leaving us with a writable data section.
> > But if we disable mseal, we only get to disable it for everything, not
> > only our libs! (and only if we re-exec with GLIBC_TUNABLES set?)
> 
> We can introduce a flag in a dynamic tag at the same time we implement
> mseal.  The flag would isntruct the dynamic linker to skip mseal.  It's
> going to be some time until link editors know about the flag, but that
> doesn't matter in your case because you have a custom linker anyway,
> more or less.

That would be the most useful, thank you. Are you thinking about some
DT_FLAGS/DT_FLAGS_1, or some other (new) tag?

Mike
  
Florian Weimer June 28, 2024, 6:06 a.m. UTC | #14
* Mike Hommey:

> On Fri, Jun 28, 2024 at 07:51:05AM +0200, Florian Weimer wrote:
>> * Mike Hommey:
>> 
>> > I just realized this can't work. For some reason I had the impression
>> > the mseal was applied to the RELRO segment, but it's over the entire
>> > library, which makes sense, in hindsight. The problem is that if we
>> > remove RELRO, then... we can't even reapply it afterwards because of the
>> > mseal, leaving us with a writable data section.
>> > But if we disable mseal, we only get to disable it for everything, not
>> > only our libs! (and only if we re-exec with GLIBC_TUNABLES set?)
>> 
>> We can introduce a flag in a dynamic tag at the same time we implement
>> mseal.  The flag would isntruct the dynamic linker to skip mseal.  It's
>> going to be some time until link editors know about the flag, but that
>> doesn't matter in your case because you have a custom linker anyway,
>> more or less.
>
> That would be the most useful, thank you. Are you thinking about some
> DT_FLAGS/DT_FLAGS_1, or some other (new) tag?

I think we'd add this to the GNU generic ABI or the Linux ABI, so we
can't use the existing flag tags.

Thanks,
Florian
  
Mike Hommey June 28, 2024, 7:39 a.m. UTC | #15
On Fri, Jun 28, 2024 at 08:06:46AM +0200, Florian Weimer wrote:
> * Mike Hommey:
> 
> > On Fri, Jun 28, 2024 at 07:51:05AM +0200, Florian Weimer wrote:
> >> * Mike Hommey:
> >> 
> >> > I just realized this can't work. For some reason I had the impression
> >> > the mseal was applied to the RELRO segment, but it's over the entire
> >> > library, which makes sense, in hindsight. The problem is that if we
> >> > remove RELRO, then... we can't even reapply it afterwards because of the
> >> > mseal, leaving us with a writable data section.
> >> > But if we disable mseal, we only get to disable it for everything, not
> >> > only our libs! (and only if we re-exec with GLIBC_TUNABLES set?)
> >> 
> >> We can introduce a flag in a dynamic tag at the same time we implement
> >> mseal.  The flag would isntruct the dynamic linker to skip mseal.  It's
> >> going to be some time until link editors know about the flag, but that
> >> doesn't matter in your case because you have a custom linker anyway,
> >> more or less.
> >
> > That would be the most useful, thank you. Are you thinking about some
> > DT_FLAGS/DT_FLAGS_1, or some other (new) tag?
> 
> I think we'd add this to the GNU generic ABI or the Linux ABI, so we
> can't use the existing flag tags.

https://sourceware.org/gnu-gabi/program-loading-and-dynamic-linking.txt
lists DT_GNU_FLAGS_1, although it's not in glibc.

Mike
  
Adhemerval Zanella July 1, 2024, 9:08 p.m. UTC | #16
On 28/06/24 04:39, Mike Hommey wrote:
> On Fri, Jun 28, 2024 at 08:06:46AM +0200, Florian Weimer wrote:
>> * Mike Hommey:
>>
>>> On Fri, Jun 28, 2024 at 07:51:05AM +0200, Florian Weimer wrote:
>>>> * Mike Hommey:
>>>>
>>>>> I just realized this can't work. For some reason I had the impression
>>>>> the mseal was applied to the RELRO segment, but it's over the entire
>>>>> library, which makes sense, in hindsight. The problem is that if we
>>>>> remove RELRO, then... we can't even reapply it afterwards because of the
>>>>> mseal, leaving us with a writable data section.
>>>>> But if we disable mseal, we only get to disable it for everything, not
>>>>> only our libs! (and only if we re-exec with GLIBC_TUNABLES set?)
>>>>
>>>> We can introduce a flag in a dynamic tag at the same time we implement
>>>> mseal.  The flag would isntruct the dynamic linker to skip mseal.  It's
>>>> going to be some time until link editors know about the flag, but that
>>>> doesn't matter in your case because you have a custom linker anyway,
>>>> more or less.
>>>
>>> That would be the most useful, thank you. Are you thinking about some
>>> DT_FLAGS/DT_FLAGS_1, or some other (new) tag?
>>
>> I think we'd add this to the GNU generic ABI or the Linux ABI, so we
>> can't use the existing flag tags.
> 
> https://sourceware.org/gnu-gabi/program-loading-and-dynamic-linking.txt
> lists DT_GNU_FLAGS_1, although it's not in glibc.

I think we can set a generic .note.gnu.property / NT_GNU_PROPERTY_TYPE_0,
it seems that now only x86 and aarch64 have specific notes so it should
be doable to reserve a number to be used across multiple ABIs.  I will
check this for v2, and the idea would be to opt-out the sealing.
  

Patch

diff --git a/elf/dl-load.c b/elf/dl-load.c
index 8a89b71016..4c2371ec46 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1431,6 +1431,8 @@  cannot enable executable stack as shared object requires");
     /* Assign the next available module ID.  */
     _dl_assign_tls_modid (l);
 
+  l->l_seal = mode & RTLD_NODELETE ? lt_seal_toseal : lt_seal_dont;
+
 #ifdef DL_AFTER_LOAD
   DL_AFTER_LOAD (l);
 #endif
diff --git a/elf/dl-mseal-mode.h b/elf/dl-mseal-mode.h
new file mode 100644
index 0000000000..7f9ede4db7
--- /dev/null
+++ b/elf/dl-mseal-mode.h
@@ -0,0 +1,29 @@ 
+/* Memory sealing.  Generic definitions.
+   Copyright (C) 2024 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 _DL_SEAL_MODE_H
+#define _DL_SEAL_MODE_H
+
+enum dl_seal_mode
+{
+  DL_SEAL_DISABLE = 0,
+  DL_SEAL_ENABLE = 1,
+  DL_SEAL_ENFORCE = 2,
+};
+
+#endif
diff --git a/elf/dl-open.c b/elf/dl-open.c
index c378da16c0..7bd90ef069 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -837,6 +837,10 @@  dl_open_worker (void *a)
   if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
     _dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
 		      new->l_name, new->l_ns, new->l_direct_opencount);
+
+  /* The seal flag is set only for NEW, however its dependencies could not be
+     unloaded and thus can also be sealed.  */
+  _dl_mseal_map (new, true);
 }
 
 void *
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index 4bf7aec88b..01f88e9003 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -28,6 +28,7 @@ 
 #include <_itoa.h>
 #include <libc-pointer-arith.h>
 #include "dynamic-link.h"
+#include <dl-mseal.h>
 
 /* Statistics function.  */
 #ifdef SHARED
@@ -347,6 +348,11 @@  _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
      done, do it.  */
   if (l->l_relro_size != 0)
     _dl_protect_relro (l);
+
+  /* Seal the memory mapping after RELRO setup, we can use the PT_LOAD
+     segments because even if relro splits the the original RW VMA,
+     mseal works with multiple VMAs with different flags.  */
+  _dl_mseal_map (l, false);
 }
 
 
@@ -369,6 +375,49 @@  cannot apply additional memory protection after relocation");
     }
 }
 
+static void
+_dl_mseal_map_1 (struct link_map *l)
+{
+  if (l->l_seal == lt_seal_sealed)
+    return;
+
+  int r = -1;
+  if (l->l_contiguous)
+    r = _dl_mseal ((void *) l->l_map_start, l->l_map_end - l->l_map_start);
+  else
+    {
+      const ElfW(Phdr) *ph;
+      for (ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph)
+	switch (ph->p_type)
+	  {
+	  case PT_LOAD:
+	    {
+	      ElfW(Addr) mapstart = l->l_addr
+		  + (ph->p_vaddr & ~(GLRO(dl_pagesize) - 1));
+	      ElfW(Addr) allocend = l->l_addr + ph->p_vaddr + ph->p_memsz;
+	      r = _dl_mseal ((void *) mapstart, allocend - mapstart);
+	    }
+	    break;
+	}
+    }
+
+  if (r == 0)
+    l->l_seal = lt_seal_sealed;
+}
+
+void
+_dl_mseal_map (struct link_map *l, bool dep)
+{
+  if (l->l_seal == lt_seal_dont || l->l_nodelete_pending)
+    return;
+
+  if (l->l_searchlist.r_list == NULL || !dep)
+    _dl_mseal_map_1 (l);
+  else
+    for (unsigned int i = 0; i < l->l_searchlist.r_nlist; ++i)
+      _dl_mseal_map_1 (l->l_searchlist.r_list[i]);
+}
+
 void
 __attribute_noinline__
 _dl_reloc_bad_type (struct link_map *map, unsigned int type, int plt)
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 451932dd03..8290a380f3 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -45,6 +45,7 @@ 
 #include <dl-find_object.h>
 #include <array_length.h>
 #include <dl-symbol-redir-ifunc.h>
+#include <dl-mseal.h>
 
 extern char *__progname;
 char **_dl_argv = &__progname;	/* This is checked for some error messages.  */
@@ -99,6 +100,7 @@  static struct link_map _dl_main_map =
     .l_used = 1,
     .l_tls_offset = NO_TLS_OFFSET,
     .l_serial = 1,
+    .l_seal = SUPPORT_MSEAL,
   };
 
 /* Namespace information.  */
@@ -340,6 +342,11 @@  _dl_non_dynamic_init (void)
   /* Setup relro on the binary itself.  */
   if (_dl_main_map.l_relro_size != 0)
     _dl_protect_relro (&_dl_main_map);
+
+  /* Seal the memory mapping after RELRO setup, we can use the PT_LOAD
+     segments because even if relro splits the the original RW VMA,
+     mseal works with multiple VMAs with different flags.  */
+  _dl_mseal_map (&_dl_main_map, false);
 }
 
 #ifdef DL_SYSINFO_IMPLEMENTATION
diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list
index 1186272c81..bf71f648e1 100644
--- a/elf/dl-tunables.list
+++ b/elf/dl-tunables.list
@@ -142,6 +142,12 @@  glibc {
       maxval: 1
       default: 0
     }
+    seal {
+      type: INT_32
+      minval: 0
+      maxval: 2
+      default: 1
+    }
   }
 
   mem {
diff --git a/elf/rtld.c b/elf/rtld.c
index e9525ea987..174389e205 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -53,6 +53,7 @@ 
 #include <dl-find_object.h>
 #include <dl-audit-check.h>
 #include <dl-call_tls_init_tp.h>
+#include <dl-mseal.h>
 
 #include <assert.h>
 
@@ -477,6 +478,7 @@  _dl_start_final (void *arg, struct dl_start_final_info *info)
   GL(dl_rtld_map).l_real = &GL(dl_rtld_map);
   GL(dl_rtld_map).l_map_start = (ElfW(Addr)) &__ehdr_start;
   GL(dl_rtld_map).l_map_end = (ElfW(Addr)) _end;
+  GL(dl_rtld_map).l_seal = 1;
   /* Copy the TLS related data if necessary.  */
 #ifndef DONT_USE_BOOTSTRAP_MAP
 # if NO_TLS_OFFSET != 0
@@ -809,7 +811,8 @@  do_preload (const char *fname, struct link_map *main_map, const char *where)
 
   args.str = fname;
   args.loader = main_map;
-  args.mode = __RTLD_SECURE;
+  /* RTLD_NODELETE enables sealing.  */
+  args.mode = __RTLD_SECURE | RTLD_NODELETE;
 
   unsigned int old_nloaded = GL(dl_ns)[LM_ID_BASE]._ns_nloaded;
 
@@ -1123,6 +1126,7 @@  rtld_setup_main_map (struct link_map *main_map)
   /* And it was opened directly.  */
   ++main_map->l_direct_opencount;
   main_map->l_contiguous = 1;
+  main_map->l_seal = 1;
 
   /* A PT_LOAD segment at an unexpected address will clear the
      l_contiguous flag.  The ELF specification says that PT_LOAD
@@ -1636,7 +1640,7 @@  dl_main (const ElfW(Phdr) *phdr,
       /* Create a link_map for the executable itself.
 	 This will be what dlopen on "" returns.  */
       main_map = _dl_new_object ((char *) "", "", lt_executable, NULL,
-				 __RTLD_OPENEXEC, LM_ID_BASE);
+				 __RTLD_OPENEXEC | RTLD_NODELETE, LM_ID_BASE);
       assert (main_map != NULL);
       main_map->l_phdr = phdr;
       main_map->l_phnum = phnum;
@@ -1964,7 +1968,7 @@  dl_main (const ElfW(Phdr) *phdr,
     RTLD_TIMING_VAR (start);
     rtld_timer_start (&start);
     _dl_map_object_deps (main_map, preloads, npreloads,
-			 state.mode == rtld_mode_trace, 0);
+			 state.mode == rtld_mode_trace, RTLD_NODELETE);
     rtld_timer_accum (&load_time, start);
   }
 
diff --git a/elf/setup-vdso.h b/elf/setup-vdso.h
index 888e1e4897..f8d9c36453 100644
--- a/elf/setup-vdso.h
+++ b/elf/setup-vdso.h
@@ -66,6 +66,7 @@  setup_vdso (struct link_map *main_map __attribute__ ((unused)),
 
       /* The vDSO is always used.  */
       l->l_used = 1;
+      l->l_seal = lt_seal_toseal;
 
       /* Initialize l_local_scope to contain just this map.  This allows
 	 the use of dl_lookup_symbol_x to resolve symbols within the vdso.
@@ -104,6 +105,8 @@  setup_vdso (struct link_map *main_map __attribute__ ((unused)),
       if (GLRO(dl_sysinfo) == DL_SYSINFO_DEFAULT)
 	GLRO(dl_sysinfo) = GLRO(dl_sysinfo_dso)->e_entry + l->l_addr;
 # endif
+
+      _dl_mseal ((void *) l->l_map_start, l->l_map_end - l->l_map_start);
     }
 #endif
 }
diff --git a/elf/tst-rtld-list-tunables.exp b/elf/tst-rtld-list-tunables.exp
index db0e1c86e9..d40a478dd7 100644
--- a/elf/tst-rtld-list-tunables.exp
+++ b/elf/tst-rtld-list-tunables.exp
@@ -15,3 +15,4 @@  glibc.rtld.dynamic_sort: 2 (min: 1, max: 2)
 glibc.rtld.enable_secure: 0 (min: 0, max: 1)
 glibc.rtld.nns: 0x4 (min: 0x1, max: 0x10)
 glibc.rtld.optional_static_tls: 0x200 (min: 0x0, max: 0x[f]+)
+glibc.rtld.seal: 1 (min: 0, max: 2)
diff --git a/include/link.h b/include/link.h
index cb0d7d8e2f..fd8e7f25bf 100644
--- a/include/link.h
+++ b/include/link.h
@@ -212,6 +212,12 @@  struct link_map
     unsigned int l_find_object_processed:1; /* Zero if _dl_find_object_update
 					       needs to process this
 					       lt_library map.  */
+    enum			/* Memory sealing status.  */
+      {
+	lt_seal_dont,		/* Do not seal the object.  */
+	lt_seal_toseal,		/* The library is marked to be sealed.  */
+	lt_seal_sealed		/* The library is sealed.  */
+      } l_seal:2;
 
     /* NODELETE status of the map.  Only valid for maps of type
        lt_loaded.  Lazy binding sets l_nodelete_active directly,
diff --git a/manual/tunables.texi b/manual/tunables.texi
index 8dd02d8149..26fba6641d 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -356,6 +356,41 @@  tests for @code{AT_SECURE} programs and not meant to be a security feature.
 The default value of this tunable is @samp{0}.
 @end deftp
 
+@deftp Tunable glibc.rtld.seal
+Sets whether to enable memory sealing during program execution.  The sealed
+memory prevents further changes to the maped memory region, such as shrinking
+or expanding, mapping another segment over a pre-existing region, or change
+the memory protection flags (check the @code{mseal} for more information).
+The sealing is done in multiple places where the memory is supposed to be
+immuatable over program execution:
+
+@itemize @bullet
+@item
+All shared library dependencies from the binary, including the read-only segments
+after @code{PT_GNU_RELRO} setup.
+
+@item
+The binary itself, including dynamic and static linked.  In both cases it is up
+either to binary or the loader to setup the sealing.
+
+@item
+The vDSO vma provided by the kernel (if existent).
+
+@item
+Any preload libraries.
+
+@item
+Any library loaded with @code{dlopen} with @code{RTLD_NODELETE} flag.
+@end itemize
+
+The tunable accepts three diferent values: @samp{0} where sealing is disabled,
+@samp{1} where sealing is enabled, and @samp{2} where sealing is enforced.  For
+the enforced mode, if the memory can not be sealed the process terminates the
+execution.
+
+The default value of this tunable is @samp{1}.
+@end deftp
+
 @node Elision Tunables
 @section Elision Tunables
 @cindex elision tunables
diff --git a/string/strerrorname_np.c b/string/strerrorname_np.c
index 042cea381c..e0e22fa79e 100644
--- a/string/strerrorname_np.c
+++ b/string/strerrorname_np.c
@@ -17,6 +17,7 @@ 
    <https://www.gnu.org/licenses/>.  */
 
 #include <stdio.h>
+#include <string.h>
 
 const char *
 strerrorname_np (int errnum)
diff --git a/sysdeps/generic/dl-mseal.h b/sysdeps/generic/dl-mseal.h
new file mode 100644
index 0000000000..d542fcac75
--- /dev/null
+++ b/sysdeps/generic/dl-mseal.h
@@ -0,0 +1,25 @@ 
+/* Memory sealing.  Generic version.
+   Copyright (C) 2024 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/>.  */
+
+static inline int
+_dl_mseal (void *addr, size_t len)
+{
+  return 0;
+}
+
+#define SUPPORT_MSEAL lt_seal_dont
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 50f58a60e3..e0d46e9177 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1017,6 +1017,12 @@  extern void _dl_relocate_object (struct link_map *map,
 /* Protect PT_GNU_RELRO area.  */
 extern void _dl_protect_relro (struct link_map *map) attribute_hidden;
 
+/* Protect MAP with mseal.  If MAP is contiguous the while region is
+   sealed, otherwise iterate over the phdr to seal each PT_LOAD.  The DEP
+   specify whether to seal the dependencies as well.  */
+extern void _dl_mseal_map (struct link_map *map, bool dep)
+     attribute_hidden;
+
 /* Call _dl_signal_error with a message about an unhandled reloc type.
    TYPE is the result of ELFW(R_TYPE) (r_info), i.e. an R_<CPU>_* value.
    PLT is nonzero if this was a PLT reloc; it just affects the message.  */
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 82d523e588..922511b4a1 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -625,6 +625,10 @@  sysdep-rtld-routines += \
   dl-sbrk \
   # sysdep-rtld-routines
 
+dl-routines += \
+  dl-mseal \
+  # dl-routines
+
 others += \
   pldd \
   # others
@@ -634,6 +638,47 @@  install-bin += \
   # install-bin
 
 $(objpfx)pldd: $(objpfx)xmalloc.o
+
+tests-static += \
+  tst-dl_mseal-static \
+  # tests-static
+
+tests += \
+  $(tests-static) \
+  tst-dl_mseal \
+  # tests
+
+modules-names += \
+  lib-tst-dl_mseal-1 \
+  lib-tst-dl_mseal-2 \
+  lib-tst-dl_mseal-dlopen-1 \
+  lib-tst-dl_mseal-dlopen-1-1 \
+  lib-tst-dl_mseal-dlopen-2 \
+  lib-tst-dl_mseal-dlopen-2-1 \
+  lib-tst-dl_mseal-preload \
+  # modules-names
+
+$(objpfx)tst-dl_mseal.out: \
+  $(objpfx)lib-tst-dl_mseal-preload.so \
+  $(objpfx)lib-tst-dl_mseal-1.so \
+  $(objpfx)lib-tst-dl_mseal-2.so \
+  $(objpfx)lib-tst-dl_mseal-dlopen-1.so \
+  $(objpfx)lib-tst-dl_mseal-dlopen-1-1.so \
+  $(objpfx)lib-tst-dl_mseal-dlopen-2.so \
+  $(objpfx)lib-tst-dl_mseal-dlopen-2-1.so
+
+tst-dl_mseal-ARGS = -- $(host-test-program-cmd)
+$(objpfx)tst-dl_mseal: $(objpfx)lib-tst-dl_mseal-1.so
+$(objpfx)lib-tst-dl_mseal-1.so: $(objpfx)lib-tst-dl_mseal-2.so
+
+$(objpfx)lib-tst-dl_mseal-dlopen-1.so: $(objpfx)lib-tst-dl_mseal-dlopen-1-1.so
+$(objpfx)lib-tst-dl_mseal-dlopen-2.so: $(objpfx)lib-tst-dl_mseal-dlopen-2-1.so
+LDFLAGS-lib-tst-dl_mseal-dlopen-1.so = \
+  -Wl,-soname,lib-tst-dl_mseal-dlopen-1.so
+LDFLAGS-lib-tst-dl_mseal-dlopen-2.so = \
+  -Wl,-soname,lib-tst-dl_mseal-dlopen-2.so
+
+tst-dl_mseal-static-ARGS = -- $(host-test-program-cmd)
 endif
 
 ifeq ($(subdir),rt)
diff --git a/sysdeps/unix/sysv/linux/dl-mseal.c b/sysdeps/unix/sysv/linux/dl-mseal.c
new file mode 100644
index 0000000000..69124b34af
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-mseal.c
@@ -0,0 +1,51 @@ 
+/* Memory sealing.  Linux version.
+   Copyright (C) 2024 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 <atomic.h>
+#include <dl-mseal.h>
+#include <dl-mseal-mode.h>
+#include <dl-tunables.h>
+#include <ldsodefs.h>
+
+int
+_dl_mseal (void *addr, size_t len)
+{
+  int32_t mode = TUNABLE_GET (glibc, rtld, seal, int32_t, NULL);
+  if (mode == DL_SEAL_DISABLE)
+    return 0;
+
+  int r;
+#if __ASSUME_MSEAL
+  r = INTERNAL_SYSCALL_CALL (mseal, addr, len, 0);
+#else
+  r = -ENOSYS;
+  static int mseal_supported = true;
+  if (atomic_load_relaxed (&mseal_supported))
+    {
+      r = INTERNAL_SYSCALL_CALL (mseal, addr, len, 0);
+      if (r == -ENOSYS)
+	atomic_store_relaxed (&mseal_supported, false);
+    }
+#endif
+  if (mode == DL_SEAL_ENFORCE && r != 0)
+    _dl_fatal_printf ("Fatal error: sealing is enforced and an error "
+		      "ocurred for the 0x%lx-0x%lx range\n",
+		      (long unsigned int) addr,
+		      (long unsigned int) addr + len);
+  return r;
+}
diff --git a/sysdeps/unix/sysv/linux/dl-mseal.h b/sysdeps/unix/sysv/linux/dl-mseal.h
new file mode 100644
index 0000000000..89b19e33c4
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-mseal.h
@@ -0,0 +1,29 @@ 
+/* Memory sealing.  Linux version.
+   Copyright (C) 2024 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/>.  */
+
+/* Seal the ADDR or size LEN to protect against modifications, such as
+   changes on the permission flags (through mprotect), remap (through
+   mmap and/or remap), shrink, destruction changes (madvise with
+   MADV_DONTNEED), or change its size.  The input has the same constraints
+   as the mseal syscall.
+
+   Return 0 in case of success or a negative value otherwise (a negative
+   errno).  */
+int _dl_mseal (void *addr, size_t len) attribute_hidden;
+
+#define SUPPORT_MSEAL lt_seal_toseal
diff --git a/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-1.c b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-1.c
new file mode 100644
index 0000000000..3bd188efe8
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-1.c
@@ -0,0 +1,19 @@ 
+/* Additional module for tst-dl_mseal test.
+   Copyright (C) 2024 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/>.  */
+
+int foo1 (void) { return 42; }
diff --git a/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-2.c b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-2.c
new file mode 100644
index 0000000000..636e9777af
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-2.c
@@ -0,0 +1,19 @@ 
+/* Additional module for tst-dl_mseal test.
+   Copyright (C) 2024 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/>.  */
+
+int bar1 (void) { return 42; }
diff --git a/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-1-1.c b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-1-1.c
new file mode 100644
index 0000000000..ef1372f47e
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-1-1.c
@@ -0,0 +1,19 @@ 
+/* Additional module for tst-dl_mseal test.
+   Copyright (C) 2024 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/>.  */
+
+int foo2_1 (void) { return 42; }
diff --git a/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-1.c b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-1.c
new file mode 100644
index 0000000000..3c2cbe6035
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-1.c
@@ -0,0 +1,19 @@ 
+/* Additional module for tst-dl_mseal test.
+   Copyright (C) 2024 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/>.  */
+
+int foo2 (void) { return 42; }
diff --git a/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-2-1.c b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-2-1.c
new file mode 100644
index 0000000000..0cd647de46
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-2-1.c
@@ -0,0 +1,19 @@ 
+/* Additional module for tst-dl_mseal test.
+   Copyright (C) 2024 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/>.  */
+
+int bar2_1 (void) { return 42; }
diff --git a/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-2.c b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-2.c
new file mode 100644
index 0000000000..f719dd3cba
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/lib-tst-dl_mseal-dlopen-2.c
@@ -0,0 +1,19 @@ 
+/* Additional module for tst-dl_mseal test.
+   Copyright (C) 2024 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/>.  */
+
+int bar2 (void) { return 42; }
diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-static.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-static.c
new file mode 100644
index 0000000000..7f26713b35
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-static.c
@@ -0,0 +1,2 @@ 
+#define TEST_STATIC
+#include "tst-dl_mseal.c"
diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal.c b/sysdeps/unix/sysv/linux/tst-dl_mseal.c
new file mode 100644
index 0000000000..72a33d04c7
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-dl_mseal.c
@@ -0,0 +1,267 @@ 
+/* Basic tests for sealing.
+   Copyright (C) 2024 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 <array_length.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xdlfcn.h>
+#include <support/xstdio.h>
+
+#define LIB_PRELOAD              "lib-tst-dl_mseal-preload.so"
+
+#define LIB_NEEDED_1             "lib-tst-dl_mseal-1.so"
+#define LIB_NEEDED_2             "lib-tst-dl_mseal-2.so"
+
+#define LIB_DLOPEN_DEFAULT       "lib-tst-dl_mseal-dlopen-1.so"
+#define LIB_DLOPEN_DEFAULT_DEP   "lib-tst-dl_mseal-dlopen-1-1.so"
+#define LIB_DLOPEN_NODELETE      "lib-tst-dl_mseal-dlopen-2.so"
+#define LIB_DLOPEN_NODELETE_DEP  "lib-tst-dl_mseal-dlopen-2-1.so"
+
+static int
+new_flags (const char flags[4])
+{
+  bool read_flag  = flags[0] == 'r';
+  bool write_flag = flags[1] == 'w';
+  bool exec_flag  = flags[2] == 'x';
+
+  write_flag = !write_flag;
+
+  return (read_flag ? PROT_READ : 0)
+	 | (write_flag ? PROT_WRITE : 0)
+	 | (exec_flag ? PROT_EXEC : 0);
+}
+
+/* Expected libraries that loader will seal.  */
+static const char *expected_sealed_libs[] =
+{
+#ifdef TEST_STATIC
+  "tst-dl_mseal-static",
+#else
+  "libc.so",
+  "ld.so",
+  "tst-dl_mseal",
+  LIB_PRELOAD,
+  LIB_NEEDED_1,
+  LIB_NEEDED_2,
+  LIB_DLOPEN_NODELETE,
+  LIB_DLOPEN_NODELETE_DEP,
+#endif
+  "[vdso]",
+};
+
+/* Libraries/VMA that could not be sealed.  */
+static const char *non_sealed_vmas[] =
+{
+  ".",				/* basename value for empty string anonymous
+				   mappings.  */
+  "[heap]",
+  "[vsyscall]",
+  "[vvar]",
+  "[stack]",
+  "zero",			/* /dev/zero  */
+#ifndef TEST_STATIC
+  "tst-dl_mseal-mod-2.so",
+  LIB_DLOPEN_DEFAULT,
+  LIB_DLOPEN_DEFAULT_DEP
+#endif
+};
+
+static int
+is_in_string_list (const char *s, const char *const list[], size_t len)
+{
+  for (size_t i = 0; i != len; i++)
+    if (strcmp (s, list[i]) == 0)
+      return i;
+  return -1;
+}
+
+static int
+handle_restart (void)
+{
+#ifndef TEST_STATIC
+  xdlopen (LIB_DLOPEN_NODELETE, RTLD_NOW | RTLD_NODELETE);
+  xdlopen (LIB_DLOPEN_DEFAULT, RTLD_NOW);
+#endif
+
+  FILE *fp = xfopen ("/proc/self/maps", "r");
+  char *line = NULL;
+  size_t linesiz = 0;
+
+  unsigned long pagesize = getpagesize ();
+
+  bool found_expected[array_length(expected_sealed_libs)] = { false };
+  while (xgetline (&line, &linesiz, fp) > 0)
+    {
+      uintptr_t start;
+      uintptr_t end;
+      char flags[5] = { 0 };
+      char name[256] = { 0 };
+      int idx;
+
+      /* The line is in the form:
+	 start-end flags offset dev inode pathname   */
+      int r = sscanf (line,
+		      "%" SCNxPTR "-%" SCNxPTR " %4s %*s %*s %*s %256s",
+		      &start,
+		      &end,
+		      flags,
+		      name);
+      TEST_VERIFY_EXIT (r == 3 || r == 4);
+
+      int found = false;
+
+      const char *libname = basename (name);
+      if ((idx = is_in_string_list (libname, expected_sealed_libs,
+				    array_length (expected_sealed_libs)))
+	   != -1)
+	{
+	  /* Check if we can change the protection flags of the segment.  */
+	  int new_prot = new_flags (flags);
+	  TEST_VERIFY_EXIT (mprotect ((void *) start, end - start,
+				      new_prot) == -1);
+	  TEST_VERIFY_EXIT (errno == EPERM);
+
+	  /* Also checks trying to map over the sealed libraries.  */
+	  {
+	    char *p = mmap ((void *) start, pagesize, new_prot,
+			    MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+	    TEST_VERIFY_EXIT (p == MAP_FAILED);
+	    TEST_VERIFY_EXIT (errno == EPERM);
+	  }
+
+	  /* And if remap is also blocked.  */
+	  {
+	    char *p = mremap ((void *) start, end - start, end - start, 0);
+	    TEST_VERIFY_EXIT (p == MAP_FAILED);
+	    TEST_VERIFY_EXIT (errno == EPERM);
+	  }
+
+	  printf ("sealed:     vma: %#" PRIxPTR "-%#" PRIxPTR " %s %s\n",
+		  start,
+		  end,
+		  flags,
+		  name);
+
+	  found_expected[idx] = true;
+	  found = true;
+	}
+
+      if (!found)
+	{
+	  if (is_in_string_list (libname, non_sealed_vmas,
+				 array_length (non_sealed_vmas)) != -1)
+	    printf ("not-sealed: vma: %#" PRIxPTR "-%#" PRIxPTR " %s %s\n",
+		    start,
+		    end,
+		    flags,
+		    name);
+	  else
+	    FAIL_EXIT1 ("unexpected vma: %#" PRIxPTR "-%#" PRIxPTR " %s %s\n",
+			start,
+			end,
+			flags,
+			name);
+	}
+    }
+  xfclose (fp);
+
+  printf ("\n");
+
+  /* Also check if all the expected sealed maps were found.  */
+  for (int i = 0; i < array_length (expected_sealed_libs); i++)
+    if (!found_expected[i])
+      FAIL_EXIT1 ("expected VMA %s not sealed\n", expected_sealed_libs[i]);
+
+  return 0;
+}
+
+static int restart;
+#define CMDLINE_OPTIONS \
+  { "restart", no_argument, &restart, 1 },
+
+static int
+do_test (int argc, char *argv[])
+{
+  /* We must have either:
+     - One or four parameters left if called initially:
+       + path to ld.so         optional
+       + "--library-path"      optional
+       + the library path      optional
+       + the application name  */
+  if (restart)
+    return handle_restart ();
+
+  /* Check the test requirements.  */
+  {
+    int r = mseal (NULL, 0, 0);
+    if (r == -1 && errno == ENOSYS)
+      FAIL_UNSUPPORTED ("mseal is not supported by the kernel");
+    else
+      TEST_VERIFY_EXIT (r == 0);
+  }
+  support_need_proc ("Reads /proc/self/maps to get stack names.");
+
+  char *spargv[9];
+  int i = 0;
+  for (; i < argc - 1; i++)
+    spargv[i] = argv[i + 1];
+  spargv[i++] = (char *) "--direct";
+  spargv[i++] = (char *) "--restart";
+  spargv[i] = NULL;
+
+  char *envvarss[3];
+  envvarss[0] = (char *) "GLIBC_TUNABLES=glibc.rtld.seal=2";
+#ifndef TEST_STATIC
+  envvarss[1] = (char *) "LD_PRELOAD=" LIB_PRELOAD;
+  envvarss[2] = NULL;
+#else
+  envvarss[1] = NULL;
+#endif
+
+  struct support_capture_subprocess result =
+    support_capture_subprogram (spargv[0], spargv, envvarss);
+  support_capture_subprocess_check (&result, "tst-dl_mseal", 0,
+				    sc_allow_stdout);
+
+  {
+    FILE *out = fmemopen (result.out.buffer, result.out.length, "r");
+    TEST_VERIFY (out != NULL);
+    char *line = NULL;
+    size_t linesz = 0;
+    while (xgetline (&line, &linesz, out))
+      printf ("%s", line);
+    fclose (out);
+  }
+
+  support_capture_subprocess_free (&result);
+
+  return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include <support/test-driver.c>