Bug 2473839 (CVE-2026-86469) - CVE-2026-86469 glib2: TOCTOU Symlink Race in `G_FILE_CREATE_REPLACE_DESTINATION` Fallback Path
Summary: CVE-2026-86469 glib2: TOCTOU Symlink Race in `G_FILE_CREATE_REPLACE_DESTINATI...
Keywords:
Status: NEW
Alias: CVE-2026-86469
Product: Security Response
Classification: Other
Component: vulnerability
Version: unspecified
Hardware: All
OS: Linux
medium
medium
Target Milestone: ---
Assignee: Product Security
QA Contact:
URL:
Whiteboard:
Depends On: 2529489 2529490
Blocks:
TreeView+ depends on / blocked
 
Reported: 2026-05-11 21:43 UTC by OSIDB Bzimport
Modified: 2026-09-07 14:53 UTC (History)
18 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Embargoed:


Attachments (Terms of Use)

Description OSIDB Bzimport 2026-05-11 21:43:02 UTC
AI_ONLY_REPORT
package: glib2-2.88.0-1.1.hum1
------
Summary: TOCTOU Symlink Race in `G_FILE_CREATE_REPLACE_DESTINATION`  
Fallback Path: When temporary-file replacement fails, the fallback path  
unlinks and reopens the destination without exclusive creation, allowing a  
local attacker to race in a symlink and redirect writes to an unintended  
target file under the victim process's permissions.
Requirements to exploit: A local attacker must be able to create entries in  
the destination directory, induce or coincide with failure of the  
temporary-file replacement path so `fallback_strategy` is taken, and win  
the race between `g_unlink()` and `g_open()`. Security impact depends on  
the victim process being able to write a target file the attacker could not  
otherwise modify.
Component affected: `glib2-2.88.0-1.1.hum1` in  
`gio/glocalfileoutputstream.c`, `handle_overwrite_open()`, when  
`g_file_replace()` is used with `G_FILE_CREATE_REPLACE_DESTINATION`
Version affected: `glib2-2.88.0-1.1.hum1`
Patch available: no released package fix established; proposed patch  
included below
Version fixed: unknown
Upstream coordination: Not notified.
CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:L - 5.3 (MEDIUM)
AV:L - exploitation requires local access to the system and filesystem  
namespace where the target path is replaced.
AC:H - the attacker must trigger a failure-only fallback path and win a  
narrow unlink/recreate race.
PR:L - the attacker needs the ability to create files or symlinks in the  
destination directory.
UI:N - no separate user interaction is required once the vulnerable code  
path is exercised.
S:U - the impact stays within the vulnerable component's security scope.
C:N - the issue does not directly expose file contents.
I:H - a successful race can redirect writes into an unintended file  
writable by the victim process.
A:L - redirected writes can corrupt files or cause limited service  
disruption.
Impact: Moderate. Under Red Hat's severity guidance, this issue can  
compromise integrity, and to a lesser extent availability, of resources  
under certain circumstances, but it is materially harder to exploit than an  
Important flaw because it is local-only, depends on an uncommon fallback  
path, and requires both attacker control of the destination directory and  
successful race timing.
Embargo: no
Reason: The issue is local, configuration-dependent, and moderately  
severe, with straightforward operational mitigations. It does not fit the  
usual profile for an embargoed remote compromise issue.
Acknowledgement: Aisle Research
Vulnerability Details: The normal replacement flow attempts to create a  
temporary file and then replace the destination. The security issue arises  
only when that temporary-file step fails and execution falls back to  
unlinking and reopening the original pathname. A focused paraphrase of the  
vulnerable flow is:
```c
tmpfd = g_mkstemp_full(tmp_filename, ...);
if (tmpfd == -1)
goto fallback_strategy;
fallback_strategy:
if (replace_destination_set) {
unlink(filename);
fd = open(filename, O_CREAT | O_WRONLY);
}
```
If an attacker recreates `filename` as a symlink after the unlink and  
before the reopen, the subsequent open follows that symlink and later  
writes land in the symlink target. That behavior contradicts the documented  
`G_FILE_CREATE_REPLACE_DESTINATION` contract to replace instead of  
following links, and it is also inconsistent with the existing  
`test_replace_symlink` expectation that the symlink target should not be  
touched. The normal path is not the issue; the exposure is specific to the  
failure-only fallback.
Steps to reproduce:
1. Build a small `LD_PRELOAD` shim that makes `open()` or `openat()` fail  
with `ENOSPC` only for paths containing `/.goutputstream-`, while leaving  
other opens unchanged. This deterministically forces `g_mkstemp_full()` to  
fail and drives execution into the fallback path.
2. Create a `victim_path` inside a directory writable by both the victim  
process and the attacker. Create a separate existing regular file with  
recognizable contents to use as `protected_target`.
3. Run an attacker loop that waits for `victim_path` to disappear and  
immediately recreates it as a symlink pointing to `protected_target`.
4. Run a victim program that calls `g_file_replace(victim_file, NULL,  
FALSE, G_FILE_CREATE_REPLACE_DESTINATION, ...)`, writes controlled bytes,  
and closes the stream.
5. Observe that the controlled bytes are written into `protected_target`,  
demonstrating that the fallback reopen followed the attacker-created  
symlink.
Mitigation:
Avoid using `g_file_replace(..., G_FILE_CREATE_REPLACE_DESTINATION, ...)`  
on paths inside directories writable by less-privileged users until a fix  
is available.

Do not perform privileged replacement writes in shared writable  
directories where another user can recreate the destination pathname.

Preventing temporary-file creation failures reduces reachability, but it  
should be treated as defense in depth rather than a complete fix.


Proposed Fix: Add `O_EXCL` when recreating the destination in the fallback  
path and fail cleanly if another filesystem object appears at that pathname  
before reopen.
```diff
diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c
@@ -1215,10 +1215,16 @@ handle_overwrite_open (...)
if (readable)
       open_flags = O_RDWR | O_CREAT | O_BINARY | O_CLOEXEC;
+        open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC;
        else

       open_flags = O_WRONLY | O_CREAT | O_BINARY | O_CLOEXEC;
+        open_flags = O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC;
        fd = g_open (filename, open_flags, mode);
        if (fd == -1)
          {
+          if (errno == EEXIST)
+            {
+              g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
+                                   _("Target file was recreated during  
replace"));
+              goto error;
+            }
            ...
          }
```


------
This report was generated using AI technology. Always review AI-generated  
content prior to use


Note You need to log in before you can comment on or make changes to this bug.