blob: 3cd97b429c640c37be63d9712b2a6b34a4155d0e [file] [log] [blame]
commit c2a84771bb63947695ea50b89160c02b36fb634d
Author: Jian Cai <jiancai@google.com>
Date: Tue Feb 2 18:47:03 2021 -0800
[llvm-objcopy] preserve file ownership when overwritten by root
As of binutils 2.36, GNU strip calls chown(2) for "sudo strip foo" and
"sudo strip foo -o foo", but no "sudo strip foo -o bar" or "sudo strip
foo -o ./foo". In other words, while "sudo strip foo -o bar" creates a
new file bar with root access, "sudo strip foo" will keep the owner and
group of foo unchanged. Currently llvm-objcopy and llvm-strip behave
differently, always changing the owner and gropu to root. The
discrepancy prevents Chrome OS from migrating to llvm-objcopy and
llvm-strip as they change file ownership and cause intended users/groups
to lose access when invoked by sudo with the following sequence
(recommended in man page of GNU strip).
1.<Link the executable as normal.>
1.<Copy "foo" to "foo.full">
1.<Run "strip --strip-debug foo">
1.<Run "objcopy --add-gnu-debuglink=foo.full foo">
This patch makes llvm-objcopy and llvm-strip follow GNU's behavior.
Link: crbug.com/1108880
diff --git a/llvm/include/llvm/Support/FileOutputBuffer.h b/llvm/include/llvm/Support/FileOutputBuffer.h
index 8eb36d0034ad..d65997201ef3 100644
--- a/llvm/include/llvm/Support/FileOutputBuffer.h
+++ b/llvm/include/llvm/Support/FileOutputBuffer.h
@@ -28,12 +28,15 @@ namespace llvm {
class FileOutputBuffer {
public:
enum {
- /// set the 'x' bit on the resulting file
+ /// Set the 'x' bit on the resulting file.
F_executable = 1,
/// Don't use mmap and instead write an in-memory buffer to a file when this
/// buffer is closed.
F_no_mmap = 2,
+
+ /// Preserve ownership if the file already exists.
+ F_keep_ownership = 4,
};
/// Factory method to create an OutputBuffer object which manages a read/write
@@ -46,7 +49,8 @@ public:
/// \p Size. It is an error to specify F_modify and Size=-1 if \p FilePath
/// does not exist.
static Expected<std::unique_ptr<FileOutputBuffer>>
- create(StringRef FilePath, size_t Size, unsigned Flags = 0);
+ create(StringRef FilePath, size_t Size, unsigned Flags = 0,
+ unsigned UserID = 0, unsigned GroupID = 0);
/// Returns a pointer to the start of the buffer.
virtual uint8_t *getBufferStart() const = 0;
diff --git a/llvm/include/llvm/Support/FileSystem.h b/llvm/include/llvm/Support/FileSystem.h
index 2483aae046f5..d82e966215dc 100644
--- a/llvm/include/llvm/Support/FileSystem.h
+++ b/llvm/include/llvm/Support/FileSystem.h
@@ -1159,6 +1159,16 @@ std::error_code unlockFile(int FD);
/// means that the filesystem may have failed to perform some buffered writes.
std::error_code closeFile(file_t &F);
+#ifdef LLVM_ON_UNIX
+/// @brief Change ownership of a file.
+///
+/// @param Owner The owner of the file to change to.
+/// @param Group The group of the file to change to.
+/// @returns errc::success if successfully updated file ownership, otherwise an
+/// error code is returned.
+std::error_code changeFileOwnership(int FD, uint32_t Owner, uint32_t Group);
+#endif
+
/// RAII class that facilitates file locking.
class FileLocker {
int FD; ///< Locked file handle.
diff --git a/llvm/lib/Support/FileOutputBuffer.cpp b/llvm/lib/Support/FileOutputBuffer.cpp
index 3342682270dc..7b2a512bd475 100644
--- a/llvm/lib/Support/FileOutputBuffer.cpp
+++ b/llvm/lib/Support/FileOutputBuffer.cpp
@@ -125,7 +125,8 @@ createInMemoryBuffer(StringRef Path, size_t Size, unsigned Mode) {
}
static Expected<std::unique_ptr<FileOutputBuffer>>
-createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) {
+createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode,
+ bool KeepOwnership, unsigned UserID, unsigned GroupID) {
Expected<fs::TempFile> FileOrErr =
fs::TempFile::create(Path + ".tmp%%%%%%%", Mode);
if (!FileOrErr)
@@ -133,6 +134,13 @@ createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) {
fs::TempFile File = std::move(*FileOrErr);
#ifndef _WIN32
+ // Try to preserve file ownership if requested.
+ if (KeepOwnership) {
+ fs::file_status Stat;
+ if (!fs::status(File.FD, Stat) && Stat.getUser() == 0)
+ fs::changeFileOwnership(File.FD, UserID, GroupID);
+ }
+
// On Windows, CreateFileMapping (the mmap function on Windows)
// automatically extends the underlying file. We don't need to
// extend the file beforehand. _chsize (ftruncate on Windows) is
@@ -163,7 +171,8 @@ createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) {
// Create an instance of FileOutputBuffer.
Expected<std::unique_ptr<FileOutputBuffer>>
-FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) {
+FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags,
+ unsigned UserID, unsigned GroupID) {
// Handle "-" as stdout just like llvm::raw_ostream does.
if (Path == "-")
return createInMemoryBuffer("-", Size, /*Mode=*/0);
@@ -196,7 +205,8 @@ FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) {
if (Flags & F_no_mmap)
return createInMemoryBuffer(Path, Size, Mode);
else
- return createOnDiskBuffer(Path, Size, Mode);
+ return createOnDiskBuffer(Path, Size, Mode, Flags & F_keep_ownership,
+ UserID, GroupID);
default:
return createInMemoryBuffer(Path, Size, Mode);
}
diff --git a/llvm/lib/Support/Unix/Path.inc b/llvm/lib/Support/Unix/Path.inc
index 77f3f54bd881..bbac8a5b3733 100644
--- a/llvm/lib/Support/Unix/Path.inc
+++ b/llvm/lib/Support/Unix/Path.inc
@@ -1211,6 +1211,14 @@ std::error_code real_path(const Twine &path, SmallVectorImpl<char> &dest,
return std::error_code();
}
+std::error_code changeFileOwnership(int FD, uint32_t Owner, uint32_t Group) {
+ auto FChown = [&]() { return ::fchown(FD, Owner, Group); };
+ // Retry if fchown call fails due to interruption.
+ if ((sys::RetryAfterSignal(-1, FChown)) < 0)
+ return std::error_code(errno, std::generic_category());
+ return std::error_code();
+}
+
} // end namespace fs
namespace path {
diff --git a/llvm/tools/llvm-objcopy/Buffer.cpp b/llvm/tools/llvm-objcopy/Buffer.cpp
index 06b2a20a762f..304979431210 100644
--- a/llvm/tools/llvm-objcopy/Buffer.cpp
+++ b/llvm/tools/llvm-objcopy/Buffer.cpp
@@ -36,7 +36,12 @@ Error FileBuffer::allocate(size_t Size) {
}
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
- FileOutputBuffer::create(getName(), Size, FileOutputBuffer::F_executable);
+ FileOutputBuffer::create(getName(), Size,
+ KeepOwnership
+ ? FileOutputBuffer::F_executable |
+ FileOutputBuffer::F_keep_ownership
+ : FileOutputBuffer::F_executable,
+ UserID, GroupID);
// FileOutputBuffer::create() returns an Error that is just a wrapper around
// std::error_code. Wrap it in FileError to include the actual filename.
if (!BufferOrErr)
diff --git a/llvm/tools/llvm-objcopy/Buffer.h b/llvm/tools/llvm-objcopy/Buffer.h
index 487d5585c364..e439e984b4f9 100644
--- a/llvm/tools/llvm-objcopy/Buffer.h
+++ b/llvm/tools/llvm-objcopy/Buffer.h
@@ -40,6 +40,9 @@ class FileBuffer : public Buffer {
// Indicates that allocate(0) was called, and commit() should create or
// truncate a file instead of using a FileOutputBuffer.
bool EmptyFile = false;
+ bool KeepOwnership = false;
+ unsigned UserID = 0;
+ unsigned GroupID = 0;
public:
Error allocate(size_t Size) override;
@@ -47,6 +50,8 @@ public:
Error commit() override;
explicit FileBuffer(StringRef FileName) : Buffer(FileName) {}
+ explicit FileBuffer(StringRef FileName, bool Keep, unsigned UID, unsigned GID)
+ : Buffer(FileName), KeepOwnership(Keep), UserID(UID), GroupID(GID) {}
};
class MemBuffer : public Buffer {
diff --git a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp
index 7fd2acd11e99..42d97b2ada5a 100644
--- a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp
+++ b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp
@@ -310,7 +310,10 @@ static Error executeObjcopy(CopyConfig &Config) {
if (Error E = executeObjcopyOnArchive(Config, *Ar))
return E;
} else {
- FileBuffer FB(Config.OutputFilename);
+ FileBuffer FB(Config.OutputFilename,
+ Config.InputFilename != "-" &&
+ Config.InputFilename == Config.OutputFilename,
+ Stat.getUser(), Stat.getGroup());
if (Error E = executeObjcopyOnBinary(Config,
*BinaryOrErr.get().getBinary(), FB))
return E;