From 0907a322f38848d4b2bebf21e2055f0190fb693a Mon Sep 17 00:00:00 2001 From: Volodymyr Zolotopupov Date: Wed, 18 Dec 2024 01:30:42 +0200 Subject: [PATCH] install: Restore SETUID/SETGID after RPATH change Most Unix-like systems drops the SETUID/SETGID bits when a file changes, so after changing the RPATH, it is necessary to restore the original file mode. --- Source/cmSystemTools.cxx | 118 +++++++++++++++++++++++-- Tests/RunCMake/file-RPATH/Common.cmake | 30 +++++++ 2 files changed, 141 insertions(+), 7 deletions(-) diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index 24ab7ae949..8d01f77039 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -1158,6 +1158,76 @@ std::string cmSystemTools::GetComspec() #endif +// File changes involve removing SETUID/SETGID bits when a file is modified. +// This behavior is consistent across most Unix-like operating systems. +class FileModeGuard +{ +public: + FileModeGuard(const std::string& file_path, std::string* emsg); + bool Restore(std::string* emsg); + bool HasErrors() const; + +private: +#ifndef _WIN32 + mode_t mode_; +#endif + std::string filepath_; +}; + +FileModeGuard::FileModeGuard(const std::string& file_path, std::string* emsg) +{ +#ifndef _WIN32 + struct stat file_stat; + if (stat(file_path.c_str(), &file_stat) != 0) { + if (emsg) { + *emsg = cmStrCat("Cannot get file stat: ", strerror(errno)); + } + return; + } + + mode_ = file_stat.st_mode; +#else + static_cast(emsg); +#endif + filepath_ = file_path; +} + +bool FileModeGuard::Restore(std::string* emsg) +{ + assert(filepath_.empty() == false); + +#ifndef _WIN32 + struct stat file_stat; + if (stat(filepath_.c_str(), &file_stat) != 0) { + if (emsg) { + *emsg = cmStrCat("Cannot get file stat: ", strerror(errno)); + } + return false; + } + + // Nothing changed; everything is in the expected state + if (file_stat.st_mode == mode_) { + return true; + } + + if (chmod(filepath_.c_str(), mode_) != 0) { + if (emsg) { + *emsg = cmStrCat("Cannot restore the file mode: ", strerror(errno)); + } + return false; + } +#else + static_cast(emsg); +#endif + + return true; +} + +bool FileModeGuard::HasErrors() const +{ + return filepath_.empty(); +} + std::string cmSystemTools::GetRealPath(const std::string& path, std::string* errorMessage) { @@ -3228,6 +3298,11 @@ cm::optional AdjustRPathELF(std::string const& file, return cmSystemTools::RemoveRPath(file, emsg, changed); } + FileModeGuard file_mode_guard(file, emsg); + if (file_mode_guard.HasErrors()) { + return false; + } + { // Open the file for update. cmsys::ofstream f(file.c_str(), @@ -3267,6 +3342,10 @@ cm::optional AdjustRPathELF(std::string const& file, } } + if (!file_mode_guard.Restore(emsg)) { + return false; + } + // Everything was updated successfully. if (changed) { *changed = true; @@ -3760,6 +3839,11 @@ static cm::optional RemoveRPathELF(std::string const& file, bytesBegin = elf.GetDynamicEntryPosition(0); } + FileModeGuard file_mode_guard(file, emsg); + if (file_mode_guard.HasErrors()) { + return false; + } + // Open the file for update. cmsys::ofstream f(file.c_str(), std::ios::in | std::ios::out | std::ios::binary); @@ -3803,6 +3887,13 @@ static cm::optional RemoveRPathELF(std::string const& file, } } + // Close the handle to allow further operations on the file + f.close(); + + if (!file_mode_guard.Restore(emsg)) { + return false; + } + // Everything was updated successfully. if (removed) { *removed = true; @@ -3821,15 +3912,28 @@ static cm::optional RemoveRPathXCOFF(std::string const& file, (void)emsg; return cm::nullopt; // Cannot handle XCOFF files. #else - cmXCOFF xcoff(file.c_str(), cmXCOFF::Mode::ReadWrite); - if (!xcoff) { - return cm::nullopt; // Not a valid XCOFF file. + bool rm = false; + + FileModeGuard file_mode_guard(file, emsg); + if (file_mode_guard.HasErrors()) { + return false; } - bool rm = xcoff.RemoveLibPath(); - if (!xcoff) { - if (emsg) { - *emsg = xcoff.GetErrorMessage(); + + { + cmXCOFF xcoff(file.c_str(), cmXCOFF::Mode::ReadWrite); + if (!xcoff) { + return cm::nullopt; // Not a valid XCOFF file. } + rm = xcoff.RemoveLibPath(); + if (!xcoff) { + if (emsg) { + *emsg = xcoff.GetErrorMessage(); + } + return false; + } + } + + if (!file_mode_guard.Restore(emsg)) { return false; } diff --git a/Tests/RunCMake/file-RPATH/Common.cmake b/Tests/RunCMake/file-RPATH/Common.cmake index d6d3eeb26f..837370d37d 100644 --- a/Tests/RunCMake/file-RPATH/Common.cmake +++ b/Tests/RunCMake/file-RPATH/Common.cmake @@ -1,3 +1,26 @@ +if(CMAKE_HOST_UNIX) + function(permissions_set f) + file(CHMOD ${f} FILE_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE SETUID) + endfunction() + function(permissions_check f) + execute_process(COMMAND sh -c "stat -c %a \"${f}\"" + OUTPUT_VARIABLE stat_out OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE stat_err + RESULT_VARIABLE stat_res + ) + if(stat_res EQUAL 0) + if(NOT stat_out STREQUAL "4700") + message(FATAL_ERROR "Expected permissions 4700 but got ${stat_out}:\n ${f}") + endif() + endif() + endfunction() +else() + function(permissions_set) + endfunction() + function(permissions_check) + endfunction() +endif() + # Prepare binaries on which to operate. set(in "${CMAKE_CURRENT_LIST_DIR}/${format}") set(out "${CMAKE_CURRENT_BINARY_DIR}") @@ -11,11 +34,14 @@ foreach(f ${static}) endforeach() foreach(f ${dynamic_files}) + permissions_set(${f}) + # Check for the initial RPATH. file(RPATH_CHECK FILE "${f}" RPATH "/sample/rpath") if(NOT EXISTS "${f}") message(FATAL_ERROR "RPATH_CHECK removed ${f}") endif() + permissions_check(${f}) # Change the RPATH. file(RPATH_CHANGE FILE "${f}" @@ -26,6 +52,7 @@ foreach(f ${dynamic_files}) if(NOT rpath) message(FATAL_ERROR "RPATH not changed in ${f}") endif() + permissions_check(${f}) # Change the RPATH without compiler defined rpath removed file(RPATH_CHANGE FILE "${f}" @@ -36,6 +63,7 @@ foreach(f ${dynamic_files}) if(NOT rpath) message(FATAL_ERROR "RPATH not updated in ${f}") endif() + permissions_check(${f}) # Change the RPATH with compiler defined rpath removed file(RPATH_CHANGE FILE "${f}" @@ -51,6 +79,7 @@ foreach(f ${dynamic_files}) if(rpath) message(FATAL_ERROR "RPATH not removed in ${f}") endif() + permissions_check(${f}) # Remove the RPATH. file(RPATH_REMOVE FILE "${f}") @@ -59,6 +88,7 @@ foreach(f ${dynamic_files}) if(rpath) message(FATAL_ERROR "RPATH not removed from ${f}") endif() + permissions_check(${f}) # Check again...this should remove the file. file(RPATH_CHECK FILE "${f}" RPATH "/sample/rpath")