This is an archive of the discontinued LLVM Phabricator instance.

[llvm-objcopy] Avoid rename if input filename = output filename
AbandonedPublic

Authored by MaskRay on Feb 8 2021, 5:46 PM.

Details

Summary

In binutils, objcopy/strip perform smart_rename for "in-place" strip exe
and objcopy exe. smart_rename calls chown(2) (vulnerable:
https://sourceware.org/bugzilla/show_bug.cgi?id=26945) which requires
additional care to restore file modes and owner/group.

binutils 2.37 is moving to use truncate+overwrite
https://sourceware.org/pipermail/binutils/2021-February/115282.html

It has nice properties that owner/group/mode/security context/xattr are all
preserved. The downside is ETXTBSY (while the executable is running) and
potentially corrupted output in the case of full disk situation and other
erroneous cases.

This patch emulates its behavior when input filename = output filename
(llvm-strip exe, llvm-strip exe -o exe)[1]. llvm-objcopy/llvm-strip
treats '-' as stdin/stdout so '-' needs to be excluded from this behavior.
In addition, the implementation of --build-id-link-{input,out} (not in binutils,
contributed by Fuchsia but not in-use) needs the on-disk file so they should be excluded as well.

Changing owner/group requires privilege to test. I merely strengthen the existing file mode tests.

[1]: I'd like to just check whether -o is present, but it would need more code, and it probably does not matter to additionally make llvm-strip exe -o exe special while llvm-strip exe -o ./exe follows our original behavior.

Diff Detail

Event Timeline

MaskRay created this revision.Feb 8 2021, 5:46 PM
MaskRay requested review of this revision.Feb 8 2021, 5:46 PM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 8 2021, 5:46 PM

Confirmed with phosek that --build-id-link-{input,out} is no longer used by Fuchsia, so I suspect the two options are not used by anyone now. However, I need to special case them to avoid breaking build-id-link-dir.test
Similarly, the - special case is needed to prevent breaking tests.

kees added a subscriber: kees.Feb 9 2021, 10:46 AM

Is it possible to plumb fd instead of pathname? Then fchown(), fsetxattr(), etc, can all be used?

Is it possible to plumb fd instead of pathname? Then fchown(), fsetxattr(), etc, can all be used?

(Before this patch both input and output filenames can already be opened more than once. TOC-TOU already applies, which is bad.)

I wanted to avoid adding a new open but it is difficult. Expected<OwningBinary<llvm::object::Binary>> BinaryOrErr = createBinary(Config.InputFilename); does not expose the file handle.
Fixing it is possible but that will be a very intrusive change.

Preserving extended attributes is possible but different OSes have different APIs, e.g. fgetxattr on Linux and extattr_get_file on FreeBSD. In the end binutils just accepts the non-atomic operation. So we just follow suit.

jcai19 added a comment.Feb 9 2021, 2:01 PM

Thanks for this patch. You mentioned the following issue in one of your comments on https://reviews.llvm.org/D93881. IIUC, this patch seems to not have taken care of this issue.

< I have mentioned that the GNU objcopy/strip smart_rename still has vulnerability and the owner preservation does not work for non-root (CAP_CHOWN) users.

< chmod o+wx .
< sudo chown mail a.o
< sudo -u bin strip a.o # owner becomes bin. llvm-strip has the same behavior.

llvm/tools/llvm-objcopy/llvm-objcopy.cpp
334

Are there any particular reasons we need to use memory buffer instead of creating a temporary file? Will this be a problem for large binary files?

MaskRay abandoned this revision.Feb 11 2021, 4:34 PM

Thanks for this patch. You mentioned the following issue in one of your comments on https://reviews.llvm.org/D93881. IIUC, this patch seems to not have taken care of this issue.

< I have mentioned that the GNU objcopy/strip smart_rename still has vulnerability and the owner preservation does not work for non-root (CAP_CHOWN) users.

< chmod o+wx .
< sudo chown mail a.o
< sudo -u bin strip a.o # owner becomes bin. llvm-strip has the same behavior.

I think my description is correct. The GNU strip chown behavior is entirely about sudo strip a / sudo strip a -o a, but not sudo strip a -o b or sudo strip a -o ./a.

What this patch does is very similar to (expected) GNU binutils 2.37 behavior, not the existing binutils behavior.

I'll abandon this patch because the more I think about it, the more I value atomic operation.