Source-side hardening. Rejects symlinks, non-regular files, and packages sitting in a world-writable parent directory without sticky bit — both shapes a non-root attacker could use to swap content under the helper before the copy below.
| 84 | // world-writable parent directory without sticky bit — both shapes a non-root attacker |
| 85 | // could use to swap content under the helper before the copy below. |
| 86 | bool validateSource(const std::string &path, off_t maxBytes) { |
| 87 | struct stat st; |
| 88 | if (::lstat(path.c_str(), &st) != 0) { |
| 89 | spdlog::error("[{}] lstat({}) failed: {}", kTagSourceRejected, path, std::strerror(errno)); |
| 90 | return false; |
| 91 | } |
| 92 | if (!S_ISREG(st.st_mode)) { |
| 93 | spdlog::error("[{}] {} is not a regular file (mode {:o})", kTagSourceRejected, path, st.st_mode); |
| 94 | return false; |
| 95 | } |
| 96 | if (st.st_size < 0 || st.st_size > maxBytes) { |
| 97 | spdlog::error("[{}] {} size {} exceeds cap {}", kTagSourceRejected, path, |
| 98 | static_cast<long long>(st.st_size), static_cast<long long>(maxBytes)); |
| 99 | return false; |
| 100 | } |
| 101 | std::filesystem::path parent = std::filesystem::path(path).parent_path(); |
| 102 | if (parent.empty()) { |
| 103 | parent = "."; |
| 104 | } |
| 105 | struct stat pst; |
| 106 | if (::stat(parent.c_str(), &pst) != 0) { |
| 107 | spdlog::error("[{}] stat({}) failed: {}", kTagSourceRejected, parent.string(), std::strerror(errno)); |
| 108 | return false; |
| 109 | } |
| 110 | if ((pst.st_mode & S_IWOTH) && !(pst.st_mode & S_ISVTX)) { |
| 111 | spdlog::error("[{}] parent dir {} is world-writable without sticky bit", kTagSourceRejected, parent.string()); |
| 112 | return false; |
| 113 | } |
| 114 | return true; |
| 115 | } |
| 116 | |
| 117 | bool readFileBytes(const std::string &path, off_t maxBytes, std::vector<uint8_t> &out) { |
| 118 | const int fd = ::open(path.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC); |
no test coverage detected