-
Notifications
You must be signed in to change notification settings - Fork 905
Protect patching with an open handle #1264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 75 commits
a210c87
295b14e
165cd18
4fc704a
547a727
4bda095
46d7a25
5fb3a15
9897f7e
08e8e05
1c5c9ae
0c74ccf
003224b
4a10fe5
5abfd2f
5c6a2c9
e6ce338
5653968
03d8f79
ef47cb2
b2b8b68
cc0b9d9
efae089
1feaf12
19a552c
dbb4d67
79d93a0
108598c
85d2ebb
964d025
aa02f9e
842e2da
92944da
a38d144
683825d
c2e56c8
3dd17e6
646cbf7
5378fcf
b479c11
2c69485
d837bc9
20f5f81
79da179
457d59a
fa529c8
15eeade
d58c662
cda6051
a6b3804
bc30666
bc19fc3
15da753
14a8a55
8de5e01
8f57df7
1edc7f6
4e5fb57
5cff23d
50ead5b
0eacfab
7cdfa80
b310dba
fa729eb
c17d592
94dd779
7d4cb93
45e9622
b5d4e19
5e10cdd
70c3800
4542d1d
2abe2aa
8974647
dcd97c7
1d049c5
7a1895a
7e575b4
6f3e498
42879e3
2e1c15b
a5c24b0
7e0a452
703950b
1e83770
ff43185
b220a20
a7103d0
4d5645f
873ce86
ffdeb52
01fc8eb
017ff0d
30364f0
9ae2c0f
7acb726
d93698b
00ac27e
41d89d1
9191dc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,14 +13,14 @@ | |
#include <sys/types.h> | ||
#include <sys/stat.h> | ||
#include <sys/mman.h> | ||
#include <dlfcn.h> | ||
#include <elf.h> | ||
#include <errno.h> | ||
#include <unistd.h> | ||
#include <fcntl.h> | ||
#include <link.h> | ||
#include <linux/limits.h> | ||
#include <sys/auxv.h> | ||
#include <link.h> | ||
#include "symbols.h" | ||
#include "dwarf.h" | ||
#include "fdtransferClient.h" | ||
|
@@ -52,8 +52,11 @@ static void applyPatch(CodeCache* cc) { | |
if (patch_libnet) { | ||
size_t len = strlen(cc->name()); | ||
if (len >= 10 && strcmp(cc->name() + len - 10, "/libnet.so") == 0) { | ||
cc->patchImport(im_poll, (void*)poll_hook); | ||
patch_libnet = false; | ||
UnloadProtection handle(cc); | ||
if (handle.isValid()) { | ||
cc->patchImport(im_poll, (void*)poll_hook); | ||
patch_libnet = false; | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -64,6 +67,25 @@ static void applyPatch(CodeCache* cc) {} | |
|
||
#endif | ||
|
||
static const void* getMainPhdr() { | ||
void* main_phdr = NULL; | ||
dl_iterate_phdr([](struct dl_phdr_info* info, size_t size, void* data) { | ||
*(const void**)data = info->dlpi_phdr; | ||
return 1; | ||
}, &main_phdr); | ||
return main_phdr; | ||
} | ||
|
||
static const void* _main_phdr = getMainPhdr(); | ||
static const char* _ld_base = (const char*)getauxval(AT_BASE); | ||
|
||
static bool isMainExecutable(const char* image_base, const void* map_end) { | ||
return _main_phdr != NULL && _main_phdr >= image_base && _main_phdr < map_end; | ||
} | ||
|
||
static bool isLoader(const char* image_base) { | ||
return _ld_base != NULL && _ld_base == image_base; | ||
} | ||
fandreuz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
class SymbolDesc { | ||
private: | ||
|
@@ -754,6 +776,14 @@ static void collectSharedLibraries(std::unordered_map<u64, SharedLibrary>& libs, | |
fclose(f); | ||
} | ||
|
||
// Strip " (deleted)" suffix so that removed library can be reopened | ||
void stripDeletedSuffix(char* name) { | ||
size_t len = strlen(name); | ||
if (len > 10 && strcmp(name + len - 10, " (deleted)") == 0) { | ||
name[len - 10] = 0; | ||
} | ||
} | ||
|
||
void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) { | ||
MutexLocker ml(_parse_lock); | ||
|
||
|
@@ -776,29 +806,13 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) { | |
std::unordered_map<u64, SharedLibrary> libs; | ||
collectSharedLibraries(libs, MAX_NATIVE_LIBS - array->count()); | ||
|
||
const char* ld_base = (const char*)getauxval(AT_BASE); | ||
if (!ld_base) { | ||
Log::warn("Cannot determine base address of the loader"); | ||
} | ||
|
||
const void* main_phdr = NULL; | ||
dl_iterate_phdr([](struct dl_phdr_info* info, size_t size, void* data) { | ||
*(const void**)data = info->dlpi_phdr; | ||
return 1; | ||
}, &main_phdr); | ||
|
||
for (auto& it : libs) { | ||
u64 inode = it.first; | ||
_parsed_inodes.insert(inode); | ||
|
||
SharedLibrary& lib = it.second; | ||
CodeCache* cc = new CodeCache(lib.file, array->count(), false, lib.map_start, lib.map_end); | ||
|
||
// Strip " (deleted)" suffix so that removed library can be reopened | ||
size_t len = strlen(lib.file); | ||
if (len > 10 && strcmp(lib.file + len - 10, " (deleted)") == 0) { | ||
lib.file[len - 10] = 0; | ||
} | ||
CodeCache* cc = new CodeCache(lib.file, array->count(), false, lib.map_start, lib.map_end, lib.image_base); | ||
stripDeletedSuffix(lib.file); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the original file has been deleted, I don't think we should attempt to parse a file with the similar name, because it can be a different file with different symbol layout. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand, this seems to be replicating the original logic. Do you propose to skip parsing the library if the original file was deleted? |
||
|
||
if (strchr(lib.file, ':') != NULL) { | ||
// Do not try to parse pseudofiles like anon_inode:name, /memfd:name | ||
|
@@ -812,24 +826,10 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) { | |
// Parse debug symbols first | ||
ElfParser::parseFile(cc, lib.image_base, lib.file, true); | ||
|
||
dlerror(); // reset any error from previous dl function calls | ||
|
||
// Protect library from unloading while parsing in-memory ELF program headers. | ||
// Also, dlopen() ensures the library is fully loaded. | ||
// Main executable and ld-linux interpreter cannot be dlopen'ed, but dlerror() returns NULL for them on some systems. | ||
void* handle = dlopen(lib.file, RTLD_LAZY | RTLD_NOLOAD); | ||
|
||
// Parse main executable and the loader (ld.so) regardless of dlopen result, since they cannot be unloaded. | ||
bool is_main_exe = main_phdr >= lib.image_base && main_phdr < lib.map_end; | ||
bool is_loader = ld_base == lib.image_base; | ||
|
||
if (handle != NULL || is_main_exe || is_loader) { | ||
UnloadProtection handle(cc); | ||
if (handle.isValid()) { | ||
ElfParser::parseProgramHeaders(cc, lib.image_base, lib.map_end, OS::isMusl()); | ||
} | ||
|
||
if (handle != NULL) { | ||
dlclose(handle); | ||
} | ||
} | ||
|
||
free(lib.file); | ||
|
@@ -845,4 +845,39 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) { | |
} | ||
} | ||
|
||
static bool isValidHandle(const CodeCache* cc, void* handle) { | ||
struct link_map* map; | ||
// validate that the current loaded library is the same library that was observed during the /proc/self/maps processing | ||
if (handle != NULL && dlinfo(handle, RTLD_DI_LINKMAP, &map) == 0) { | ||
return cc->imageBase() == (const char*)map->l_addr; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this apply to shared objects whose virtual load address is non-zero? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure, I think @Baraa-Hasheesh looked into this as part of #1261. Can you weigh in? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apangin yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm interested in a situation where a shared object has There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apangin I will investigate this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, the problem can happen even in a single-threaded cases, for instance, if we attempt to do native memory profiling long after a library was first parsed. I do think we need some level of protection against this situation, even if it is not 100% accurate. If comparing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apangin technically that's true but it will require that the library is reloaded into a different location in memory (could happen in JIT from what I'm reading) But this raises the concern that the library will not be patched (skipped) or loose it's patching during these long profile sessions (profiling accuracy degrades with time) while being a valid behavior of the profiled process (Different issue that can be tracked separately) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about we drop the dlinfo & switch to dladdr
We can use the We use that pointer to call
This solution should be also applicable to MacOs Note: I initially didn't consider There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just note that this is only valid on the patching side, while the mentioned case here will still be possible if lib was relocated after it was detected but before it was parsed I think it's best if we have the dladdr side on the patching side as it should also protect MacOs For the parsing side, for now I think it should be an acceptable risk & we can create a different thread to track & reproduce the possible crash There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bara's fix was implemented in 42879e3 |
||
} | ||
return false; | ||
} | ||
|
||
UnloadProtection::UnloadProtection(const CodeCache *cc) { | ||
_lib_handle = NULL; | ||
_valid = false; | ||
fandreuz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (OS::isMusl() || isMainExecutable(cc->imageBase(), cc->maxAddress()) || isLoader(cc->imageBase())) { | ||
_valid = true; | ||
return; | ||
} | ||
|
||
char* nameCopy = strdup(cc->name()); | ||
fandreuz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
stripDeletedSuffix(nameCopy); | ||
|
||
// Protect library from unloading while parsing in-memory ELF program headers. | ||
// Also, dlopen() ensures the library is fully loaded. | ||
_lib_handle = dlopen(nameCopy, RTLD_LAZY | RTLD_NOLOAD); | ||
_valid = isValidHandle(cc, _lib_handle); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not the best name, IMO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'll think about it, let me know if you have any preference There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
free(nameCopy); | ||
} | ||
|
||
UnloadProtection::~UnloadProtection() { | ||
if (_lib_handle != NULL) { | ||
dlclose(_lib_handle); | ||
} | ||
} | ||
|
||
#endif // __linux__ |
Uh oh!
There was an error while loading. Please reload this page.