[PATCH] D56806: [llvm-objcopy] Fix crash when writing empty binary output
Jordan Rupprecht via Phabricator via llvm-commits
llvm-commits at lists.llvm.org
Thu Jan 24 09:33:12 PST 2019
rupprecht marked 2 inline comments as done.
rupprecht added inline comments.
================
Comment at: llvm/tools/llvm-objcopy/Buffer.cpp:31
+ if (std::error_code EC = sys::Process::SafelyCloseFileDescriptor(FD))
+ reportError(FileName, EC);
+}
----------------
jhenderson wrote:
> rupprecht wrote:
> > rupprecht wrote:
> > > alexshap wrote:
> > > > rupprecht wrote:
> > > > > alexshap wrote:
> > > > > > so given we are trying to get rid of "reportError" (to enable us to build a library from this code in the future), maybe we can return an error from the method "allocate" instead ? (similarly to what "commit" does)
> > > > > SGTM, though I'd like to punt on changing the signature of allocate() in this patch. I'll start working on a separate patch now...
> > > > oh, probably my bad,
> > > > my second thought is the following: basically if i remember correctly we call Buffer::commit in the very end (and in the case of FileBuffer it does smth like renaming the temporary file backing the buffer (which was mmaped into the memory) - i don't remember precisely, need to double check it).
> > > > If there is an error in the tool somewhere in the middle of the whole pipeline, the files are untouched and I think it's a nice property to have. But if we truncate a file inside the method "allocate" this won't be true anymore. So ideally all the "real" changes should be applied to the file system only inside the "commit" method + would be cool to have a test for this scenario. What do you think ?
> > > No worries, error handling here looks like something we should fix. I have something in progress (https://reviews.llvm.org/differential/diff/182462/) but I actually want to increase the scope of that refactoring, so it's not ready yet. I just want it to be orthogonal to this crash fix :)
> > >
> > > I see what you mean about commit(). In the instance of the crash I'm fixing, it doesn't seem like it's at all possible for a crash in between finalize() and write(), but I can't say I've ruled out other situations where Size = 0.
> > > I don't know if it's testable, but I'll try to move this block to commit().
> > I couldn't come up with a way to add a test for this, but I manually verified it by throwing in an assert(false) and making sure the file isn't changed.
> >
> > ```
> > $ echo abcd > /tmp/existing-file.txt
> > $ llvm-objcopy -R .text -O binary binary-output-empty.test.tmp.o /tmp/existing-file.txt
> > <crash>
> > $ cat /tmp/existing-file.txt
> > abcd
> > ```
> > (Before your suggestion, it was printing an empty file)
> >
> > Given the state of the code, I don't think a crash is really possible -- unless something like malloc failed -- so I don't have a test to offer.
> This is probably fine as is, but a couple of ideas I had where this might fail (not 100% sure) is a) if another file has a lock on it, or b) if the file is read-only maybe? Are either of those testable in the current framework? I think you could test a) by using python to open it and then spawn llvm-objcopy without closing the file.
I think both of those would describe createAndTruncateFile() failures. If commit() fails, then that's "fine" in the sense that there are no side effects -- any file modification should fail. The original problem was:
# allocate() is called, which truncates some existing data
# some extra code is called, which crashes
# commit() is never called, so the processed object is never written, but the original data is still destroyed
vs as implemented in the latest version of the patch:
# allocate() is called, which remembers to defer truncating until later
# some extra code is called, which crashes
# commit() is never called, so truncation never happens and the original data is still there
But, I don't see any reasonable ways for the part in between to crash. The relevent snippet I'm trying to break is this:
```
// From finalize()
for (auto &Section : Obj.sections()) {
if ((Section.Flags & SHF_ALLOC) == 0)
continue;
AllocatedSections.push_back(&Section);
}
TotalSize = 0;
for (const auto &Section : AllocatedSections) {
if (Section->Type != SHT_NOBITS)
TotalSize = std::max(TotalSize, Section->Offset + Section->Size);
}
Buf.allocate(TotalSize);
SecWriter = llvm::make_unique<BinarySectionWriter>(Buf);
// From write()
for (auto &Section : Obj.sections()) {
if ((Section.Flags & SHF_ALLOC) == 0)
continue;
Section.accept(*SecWriter);
}
return Buf.commit();
```
The things that could fail:
- SecWriter construction fails, e.g. malloc failure
- Section.accept() fails, but given the constraint that TotalSize is 0 (in order for allocate(0) to be called), the only sections it could be called on would be SHT_NOBITS or have zero offset/size. It looks like section visitors check SHT_NOBITS and return early, and I don't know if zero offset/size is valid or possible to construct.
================
Comment at: llvm/tools/llvm-objcopy/Buffer.cpp:57
+
+ assert(Buf && "allocate() not called before commit()!");
Error Err = Buf->commit();
----------------
jhenderson wrote:
> Instead of this assert, how practical would it be to call allocate in the constructor?
allocate() is often called long after construction, e.g. llvm-objcopy.cpp creates the FileBuffer and ELF/Object.cpp calls allocate() after the object has been processed (and then it knows how much to allocate).
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D56806/new/
https://reviews.llvm.org/D56806
More information about the llvm-commits
mailing list