Re: [RFC PATCH v1 1/2] fs: Add O_DENY_WRITE

Andy Lutomirski <[email protected]> Mon, 25 Aug 2025 14:56:18 -0700
Newsgroups com.openwall.lists.kernel-hardening,org.kernel.vger.linux-api,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-integrity,org.kernel.vger.linux-kernel,org.kernel.vger.linux-security-module
Message-ID <[email protected]>
=EF=BB=BF
> On Aug 25, 2025, at 11:10=E2=80=AFAM, Jeff Xu <[email protected]> wrote:
>=20
> =EF=BB=BFOn Mon, Aug 25, 2025 at 9:43=E2=80=AFAM Andy Lutomirski <luto@ama=
capital.net> wrote:
>>> On Mon, Aug 25, 2025 at 2:31=E2=80=AFAM Micka=C3=ABl Sala=C3=BCn <mic@di=
gikod.net> wrote:
>>> On Sun, Aug 24, 2025 at 11:04:03AM -0700, Andy Lutomirski wrote:
>>>> On Sun, Aug 24, 2025 at 4:03=E2=80=AFAM Micka=C3=ABl Sala=C3=BCn <mic@d=
igikod.net> wrote:
>>>>> On Fri, Aug 22, 2025 at 09:45:32PM +0200, Jann Horn wrote:
>>>>>> On Fri, Aug 22, 2025 at 7:08=E2=80=AFPM Micka=C3=ABl Sala=C3=BCn <mic=
@digikod.net> wrote:
>>>>>>> Add a new O_DENY_WRITE flag usable at open time and on opened file (=
e.g.
>>>>>>> passed file descriptors).  This changes the state of the opened file=
 by
>>>>>>> making it read-only until it is closed.  The main use case is for sc=
ript
>>>>>>> interpreters to get the guarantee that script' content cannot be alt=
ered
>>>>>>> while being read and interpreted.  This is useful for generic distro=
s
>>>>>>> that may not have a write-xor-execute policy.  See commit a5874fde3c=
08
>>>>>>> ("exec: Add a new AT_EXECVE_CHECK flag to execveat(2)")
>>>>>>> Both execve(2) and the IOCTL to enable fsverity can already set this=

>>>>>>> property on files with deny_write_access().  This new O_DENY_WRITE m=
ake
>>>>>> The kernel actually tried to get rid of this behavior on execve() in
>>>>>> commit 2a010c41285345da60cece35575b4e0af7e7bf44.; but sadly that had
>>>>>> to be reverted in commit 3b832035387ff508fdcf0fba66701afc78f79e3d
>>>>>> because it broke userspace assumptions.
>>>>> Oh, good to know.
>>>>>>> it widely available.  This is similar to what other OSs may provide
>>>>>>> e.g., opening a file with only FILE_SHARE_READ on Windows.
>>>>>> We used to have the analogous mmap() flag MAP_DENYWRITE, and that was=

>>>>>> removed for security reasons; as
>>>>>> https://man7.org/linux/man-pages/man2/mmap.2.html says:
>>>>>> |        MAP_DENYWRITE
>>>>>> |               This flag is ignored.  (Long ago=E2=80=94Linux 2.0 an=
d earlier=E2=80=94it
>>>>>> |               signaled that attempts to write to the underlying fil=
e
>>>>>> |               should fail with ETXTBSY.  But this was a source of d=
enial-
>>>>>> |               of-service attacks.)"
>>>>>> It seems to me that the same issue applies to your patch - it would
>>>>>> allow unprivileged processes to essentially lock files such that othe=
r
>>>>>> processes can't write to them anymore. This might allow unprivileged
>>>>>> users to prevent root from updating config files or stuff like that i=
f
>>>>>> they're updated in-place.
>>>>> Yes, I agree, but since it is the case for executed files I though it
>>>>> was worth starting a discussion on this topic.  This new flag could be=

>>>>> restricted to executable files, but we should avoid system-wide locks
>>>>> like this.  I'm not sure how Windows handle these issues though.
>>>>> Anyway, we should rely on the access control policy to control write a=
nd
>>>>> execute access in a consistent way (e.g. write-xor-execute).  Thanks f=
or
>>>>> the references and the background!
>>>> I'm confused.  I understand that there are many contexts in which one
>>>> would want to prevent execution of unapproved content, which might
>>>> include preventing a given process from modifying some code and then
>>>> executing it.
>>>> I don't understand what these deny-write features have to do with it.
>>>> These features merely prevent someone from modifying code *that is
>>>> currently in use*, which is not at all the same thing as preventing
>>>> modifying code that might get executed -- one can often modify
>>>> contents *before* executing those contents.
>>> The order of checks would be:
>>> 1. open script with O_DENY_WRITE
>>> 2. check executability with AT_EXECVE_CHECK
>>> 3. read the content and interpret it
>> Hmm.  Common LSM configurations should be able to handle this without
>> deny write, I think.  If you don't want a program to be able to make
>> their own scripts, then don't allow AT_EXECVE_CHECK to succeed on a
>> script that the program can write.
> Yes, Common LSM could handle this, however, due to historic and app
> backward compability reason, sometimes it is impossible to enforce
> that kind of policy in practice, therefore as an alternative, a
> machinism such as AT_EXECVE_CHECK is really useful.

Can you clarify?  I=E2=80=99m suspicious that we=E2=80=99re taking past each=
 other.

AT_EXECVE_CHECK solves a problem that there are actions that effectively =E2=
=80=9Cexecute=E2=80=9D a file that don=E2=80=99t execute literal CPU instruc=
tions for it. Sometimes open+read has the effect of interpreting the content=
s of the file as something code-like.

But, as I see it, deny-write is almost entirely orthogonal. If you open a fi=
le with the intent of executing it (mmap-execute or interpret =E2=80=94 make=
s little practical difference here), then the kernel can enforce some policy=
. If the file is writable by a process that ought not have permission to exe=
cute code in the context of the opening-for-execute process, then LSMs need d=
eny-write to be enforced so that they can verify the contents at the time of=
 opening.

But let=E2=80=99s step back a moment: is there any actual sensible security p=
olicy that does this?  If I want to *enforce* that a process only execute ap=
proved code, then wouldn=E2=80=99t I do it be only allowing executing files t=
hat the process can=E2=80=99t write?

The reason that the removal of deny-write wasn=E2=80=99t security =E2=80=94 i=
t was a functionality issue: a linker accidentally modified an in-use binary=
. If you have permission to use gcc or lld, etc to create binaries, and you h=
ave permission to run them, then you pretty much have permission to run what=
ever code you like.

So, if there=E2=80=99s a real security use case for deny-write, I=E2=80=99m s=
till not seeing it.

>> Keep in mind that trying to lock this down too hard is pointless for
>> users who are allowed to to ptrace-write to their own processes.  Or
>> for users who can do JIT, or for users who can run a REPL, etc.
> The ptrace-write and /proc/pid/mem writing are on my radar, at least
> for ChomeOS and Android.
> AT_EXECVE_CHECK is orthogonal to those IMO, I hope eventually all
> those paths will be hardened.
>=20
> Thanks and regards,
> -Jeff