Skip to content

Commit 54f8675

Browse files
authored
Add PEM command line flag for enforcing a max file size for ElfReader (#2194)
Summary: Add PEM command line flag for enforcing a max file size for ElfReader While memory profiling the PEM, I noticed that large binaries caused the PEM's ElfReader code to make > 100 MiB allocations. This PR adds a cli flag (`--elf_reader_max_file_size`) that allows memory conscious users to prevent the PEM from parsing binaries that would cause these large memory allocations. Relevant Issues: N/A Type of change: /kind feature Test Plan: Existing tests and skaffold Changelog Message: Added `--elf_reader_max_file_size` flag to PEM. This flag allows memory conscious users to prevent the PEM from parsing large binaries in order to avoid large memory allocations. --------- Signed-off-by: Dom Del Nano <[email protected]>
1 parent 301198f commit 54f8675

File tree

5 files changed

+77
-32
lines changed

5 files changed

+77
-32
lines changed

src/common/fs/fs_wrapper.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,15 @@ StatusOr<int64_t> SpaceAvailableInBytes(const std::filesystem::path& path) {
175175
return si.available;
176176
}
177177

178+
StatusOr<std::uintmax_t> GetFileSize(const std::string& binary_path) {
179+
PX_ASSIGN_OR_RETURN(const auto stat, fs::Stat(binary_path));
180+
if (stat.st_size < 0) {
181+
return error::Internal("stat() returned negative file size $0 for file $1", stat.st_size,
182+
binary_path);
183+
}
184+
return stat.st_size;
185+
}
186+
178187
StatusOr<bool> IsEmpty(const std::filesystem::path& f) {
179188
std::error_code ec;
180189
bool val = std::filesystem::is_empty(f, ec);

src/common/fs/fs_wrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ Status Chown(const std::filesystem::path& path, const uid_t uid, const gid_t gid
6969
StatusOr<struct stat> Stat(const std::filesystem::path& path);
7070
StatusOr<int64_t> SpaceAvailableInBytes(const std::filesystem::path& path);
7171

72+
StatusOr<std::uintmax_t> GetFileSize(const std::string& binary_path);
73+
7274
StatusOr<bool> IsEmpty(const std::filesystem::path& path);
7375

7476
StatusOr<std::filesystem::path> Absolute(const std::filesystem::path& path);

src/stirling/obj_tools/elf_reader.cc

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
#include "src/common/fs/fs_wrapper.h"
3333
#include "src/stirling/obj_tools/init.h"
3434

35+
DEFINE_int64(elf_reader_max_file_size, 0,
36+
"Maximum file size in bytes for ELF files. Default value of 0 means all files will "
37+
"be parsed regardless of size.");
38+
3539
namespace px {
3640
namespace stirling {
3741
namespace obj_tools {
@@ -46,6 +50,41 @@ struct LowercaseHex {
4650
};
4751
} // namespace
4852

53+
StatusOr<std::unique_ptr<ElfReader>> ElfReader::CreateImpl(
54+
const std::string& binary_path, const std::filesystem::path& debug_file_dir) {
55+
VLOG(1) << absl::Substitute("Creating ElfReader, [binary=$0] [debug_file_dir=$1]", binary_path,
56+
debug_file_dir.string());
57+
58+
auto elf_reader = std::unique_ptr<ElfReader>(new ElfReader);
59+
elf_reader->binary_path_ = binary_path;
60+
61+
if (!elf_reader->elf_reader_.load_header_and_sections(binary_path)) {
62+
return error::Internal("Can't find or process ELF file $0", binary_path);
63+
}
64+
65+
// Check for external debug symbols.
66+
Status s = elf_reader->LocateDebugSymbols(debug_file_dir);
67+
if (s.ok()) {
68+
std::string debug_symbols_path = elf_reader->debug_symbols_path_.string();
69+
70+
bool internal_debug_symbols =
71+
fs::Equivalent(elf_reader->debug_symbols_path_, binary_path).ConsumeValueOr(true);
72+
73+
// If external debug symbols were found, load that ELF info instead.
74+
if (!internal_debug_symbols) {
75+
std::string debug_symbols_path = elf_reader->debug_symbols_path_.string();
76+
LOG(INFO) << absl::Substitute("Found debug symbols file $0 for binary $1", debug_symbols_path,
77+
binary_path);
78+
elf_reader->elf_reader_.load_header_and_sections(debug_symbols_path);
79+
return elf_reader;
80+
}
81+
}
82+
83+
// Debug symbols were either in the binary, or no debug symbols were found,
84+
// so return original elf_reader.
85+
return elf_reader;
86+
}
87+
4988
Status ElfReader::LocateDebugSymbols(const std::filesystem::path& debug_file_dir) {
5089
std::string build_id;
5190
std::string debug_link;
@@ -158,40 +197,22 @@ Status ElfReader::LocateDebugSymbols(const std::filesystem::path& debug_file_dir
158197
return error::Internal("Could not find debug symbols for $0", binary_path_);
159198
}
160199

161-
// TODO(oazizi): Consider changing binary_path to std::filesystem::path.
162200
StatusOr<std::unique_ptr<ElfReader>> ElfReader::Create(
163201
const std::string& binary_path, const std::filesystem::path& debug_file_dir) {
164-
VLOG(1) << absl::Substitute("Creating ElfReader, [binary=$0] [debug_file_dir=$1]", binary_path,
165-
debug_file_dir.string());
166-
auto elf_reader = std::unique_ptr<ElfReader>(new ElfReader);
167-
168-
elf_reader->binary_path_ = binary_path;
169-
170-
if (!elf_reader->elf_reader_.load_header_and_sections(binary_path)) {
171-
return error::Internal("Can't find or process ELF file $0", binary_path);
172-
}
173-
174-
// Check for external debug symbols.
175-
Status s = elf_reader->LocateDebugSymbols(debug_file_dir);
176-
if (s.ok()) {
177-
std::string debug_symbols_path = elf_reader->debug_symbols_path_.string();
178-
179-
bool internal_debug_symbols =
180-
fs::Equivalent(elf_reader->debug_symbols_path_, binary_path).ConsumeValueOr(true);
181-
182-
// If external debug symbols were found, load that ELF info instead.
183-
if (!internal_debug_symbols) {
184-
std::string debug_symbols_path = elf_reader->debug_symbols_path_.string();
185-
LOG(INFO) << absl::Substitute("Found debug symbols file $0 for binary $1", debug_symbols_path,
186-
binary_path);
187-
elf_reader->elf_reader_.load_header_and_sections(debug_symbols_path);
188-
return elf_reader;
202+
if (FLAGS_elf_reader_max_file_size != 0) {
203+
PX_ASSIGN_OR_RETURN(int64_t file_size, fs::GetFileSize(binary_path));
204+
if (file_size > FLAGS_elf_reader_max_file_size) {
205+
return error::Internal(
206+
"File size $0 exceeds ElfReader's max file size $1. Refusing to process file", file_size,
207+
FLAGS_elf_reader_max_file_size);
189208
}
190209
}
210+
return CreateImpl(binary_path, debug_file_dir);
211+
}
191212

192-
// Debug symbols were either in the binary, or no debug symbols were found,
193-
// so return original elf_reader.
194-
return elf_reader;
213+
StatusOr<std::unique_ptr<ElfReader>> ElfReader::CreateUncapped(
214+
const std::string& binary_path, const std::filesystem::path& debug_file_dir) {
215+
return CreateImpl(binary_path, debug_file_dir);
195216
}
196217

197218
StatusOr<ELFIO::section*> ElfReader::SymtabSection() {

src/stirling/obj_tools/elf_reader.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ namespace px {
3737
namespace stirling {
3838
namespace obj_tools {
3939

40+
constexpr std::string_view kDebugFileDir = "/usr/lib/debug";
41+
4042
class ElfReader {
4143
public:
4244
/**
@@ -50,8 +52,14 @@ class ElfReader {
5052
* @return error if could not setup elf reader.
5153
*/
5254
static StatusOr<std::unique_ptr<ElfReader>> Create(
53-
const std::string& binary_path,
54-
const std::filesystem::path& debug_file_dir = "/usr/lib/debug");
55+
const std::string& binary_path, const std::filesystem::path& debug_file_dir = kDebugFileDir);
56+
57+
/**
58+
* Creates an ElfReader that does not enforce the max file size limit. This is useful for cases
59+
* where the binary size is known in advance or the binary must be loaded regardless of size.
60+
*/
61+
static StatusOr<std::unique_ptr<ElfReader>> CreateUncapped(
62+
const std::string& binary_path, const std::filesystem::path& debug_file_dir = kDebugFileDir);
5563

5664
std::filesystem::path& debug_symbols_path() { return debug_symbols_path_; }
5765

@@ -207,6 +215,9 @@ class ElfReader {
207215
}
208216

209217
private:
218+
static StatusOr<std::unique_ptr<ElfReader>> CreateImpl(
219+
const std::string& binary_path, const std::filesystem::path& debug_file_dir);
220+
210221
ElfReader() = default;
211222

212223
StatusOr<ELFIO::section*> SymtabSection();

src/stirling/source_connectors/socket_tracer/socket_trace_bpf_tables.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ ConnInfoMapManager::ConnInfoMapManager(bpf_tools::BCCWrapper* bcc)
5151
: conn_info_map_(WrappedBCCMap<uint64_t, struct conn_info_t>::Create(bcc, "conn_info_map")),
5252
conn_disabled_map_(WrappedBCCMap<uint64_t, uint64_t>::Create(bcc, "conn_disabled_map")) {
5353
std::filesystem::path self_path = GetSelfPath().ValueOrDie();
54-
auto elf_reader_or_s = obj_tools::ElfReader::Create(self_path.string());
54+
// Opt out of the max file size restriction as this is a self-probe and must attach for
55+
// stirling to work.
56+
auto elf_reader_or_s = obj_tools::ElfReader::CreateUncapped(self_path.string());
5557
if (!elf_reader_or_s.ok()) {
5658
LOG(FATAL) << "Failed to create ElfReader for self probe";
5759
}

0 commit comments

Comments
 (0)