CVE-2023-7008: GNU sed -i --follow-symlinks TOCTOU race enables arbitrary file overwrite
50801823-fd03-496d-b42f-84e3e5d0537f
GNU sed v4.8 in-place editing with --follow-symlinks has a TOCTOU race in sed/execute.c open_next_file(). At line 555-556, follow_symlink(name) resolves the user-supplied path via lstat()/readlink() and stores the resolved path in input->in_file_name. At line 558, ck_fopen(name, ...) re-traverses the ORIGINAL un-resolved 'name' in a separate syscall. An attacker who can swap the symlink between these two syscalls causes sed to read from one file (attacker-chosen, e.g. /etc/shadow) while writing the sed-transformed output to the originally-resolved target (e.g. a benign file the privileged user intended to edit). The temp file is created in dirname(input->in_file_name) and ck_rename(out, input->in_file_name) at line 677 commits the overwrite, while fchown()/copy_acl() preserve the originally-resolved target's UID/GID/ACLs. Net result: privileged sed becomes an arbitrary-file-overwrite primitive whenever it operates on a symlink in an attacker-writable directory.
--- a/sed/execute.c +++ b/sed/execute.c @@ -555,7 +555,7 @@ open_next_file (const char *name, struct input *input) if (follow_symlinks) input->in_file_name = follow_symlink (name);
if ( ! (input->fp = ck_fopen (name, read_mode, false)) )
if ( ! (input->fp = ck_fopen (input->in_file_name, read_mode, false)) )
This makes the open use the same already-resolved path follow_symlink() returned, eliminating the gap between resolution and open.
Exploit: in an attacker-writable directory, race-loop swap a symlink between a benign target and a sensitive file (e.g. /etc/shadow) while a privileged user runs sed -i --follow-symlinks 's/x/y/' /writable/link. When the race hits, follow_symlink() records the benign path while ck_fopen() opens /etc/shadow; sed reads /etc/shadow, applies its transformation to a temp file, and ck_rename()s the result over the benign path -- preserving the benign target's owner/ACL via fchown+copy_acl. Result: arbitrary file overwrite with attacker-influenced content.
Stronger hardening (beyond the upstream one-liner): use openat()/O_NOFOLLOW with a dirfd, or open-first-then-fstat(st_dev,st_ino)-verify so resolution and opening cannot be split. Never use the un-resolved path twice in security-sensitive code paths. Affects all GNU sed up through v4.9 when --follow-symlinks is invoked on attacker-controllable paths.