]> git.ipfire.org Git - thirdparty/elfutils.git/commit
elfcompress: Handle existing output files (symlinks)
authorMark Wielaard <mark@klomp.org>
Thu, 11 Jun 2026 16:45:20 +0000 (18:45 +0200)
committerMark Wielaard <mark@klomp.org>
Thu, 18 Jun 2026 22:53:59 +0000 (00:53 +0200)
commit0e8b57aa36d80fd8e366c6e22f5bc96fdfe7c062
tree6fbc508c7985c958833397f6a59a828a7365cd5e
parentedc620b12549cf21466ce8f65ebf049d6f29278f
elfcompress: Handle existing output files (symlinks)

With -o elfcompress opens the output file with O_WRONLY and O_CREAT.
If the output file already existed then without O_TRUNC the file is
written from the start, but keeps all existing data. That means the
file might contain extra data if the (compressed) ELF file is shorter
than the existing file. Without -o the input file is the output file
and we create a temp file to create the output which should then be
atomically replaced using rename. This might fail when the input file
was a symlink (to a file in a different directory.)

An additional complication is that we were using full path based calls
for open, rename and unlink (in case we had to remove the file again).
This can go wrong if between the open and the unlink a path element is
changed by a different process. To prevent renaming or unlinking the
wrong file we have to first open the target directory and only then
create a file inside it with openat. And use the directory fd with
renameat or unlinkat when we have to rename or delete the file.

So to solve all these issues we first try to pin the directory of the
output file. Then try to open the output file with O_EXCL. Which makes
sure we created the file so we can write directly to it. If that fails
then the output file already exists. Then we first resolve the full
file path (in case the output file is a symlink) and then open the
(resolved) directory where the output should go. We then make sure to
create a temporary file inside this directory using a new system.h
helper function xmkstempat. Finally, after the output file is fully
created (and flushed out to disk) we use renameat to atomically
replace the output file with our temp file. Since we still have the
directory open this works even if the file path is changed while
creating the temp file data. On failure we use unlinkat with the
directory fd to remove the output file.

Add a new run-compress-test-out.sh testcase for input and/or output
file being symlinks and/or pointing to the same file.

* configure.ac: Check for getentropy, sys/random.h and
getrandom.
* lib/system.h (read_retry): New static inline function.
(xrandom64): Likewise.
(xmkstempat): Likewise.
* src/elfcompress.c (process_file): Declare new variables to
keep copies of the (ouput) directory, dirfd, base file and
temporary file name. Track whether we need to unlink_fnew. Fix
trailing \n in error fmt. Call open with O_EXCL for output
file. Use realpath and xmkstempat if output file exists. fsync
temp file before caling renameat. Use unlinkat in
cleanup. close and free new temporaries.
* tests/run-compress-test-out.sh: New test file.
* tests/Makefile.am (TESTS): Add new test file.
(EXTRA_DIST): Likewise.

Signed-off-by: Mark Wielaard <mark@klomp.org>
configure.ac
lib/system.h
src/elfcompress.c
tests/Makefile.am
tests/run-compress-test-out.sh [new file with mode: 0755]