-
Notifications
You must be signed in to change notification settings - Fork 19
src: add GetBuildId helper function #355
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
base: node-v24.x-nsolid-v6.x
Are you sure you want to change the base?
Changes from all commits
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 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,138 @@ | ||||||||||
| #include "nsolid_elf_utils.h" | ||||||||||
| #include "nsolid_api.h" | ||||||||||
| #include "nsolid_util.h" | ||||||||||
|
|
||||||||||
| #include <fcntl.h> | ||||||||||
| #include <elf.h> | ||||||||||
| #include <libelf.h> | ||||||||||
| #include <gelf.h> | ||||||||||
| #include <unistd.h> | ||||||||||
| #include <cstdint> | ||||||||||
| #include <cstring> | ||||||||||
| #include <limits> | ||||||||||
| #include <unordered_map> | ||||||||||
| #include "uv.h" | ||||||||||
|
|
||||||||||
| namespace node { | ||||||||||
| namespace nsolid { | ||||||||||
| namespace elf_utils { | ||||||||||
|
|
||||||||||
| namespace { | ||||||||||
|
|
||||||||||
| bool ParseBuildIdNotes(const void* buffer, size_t size, std::string* build_id) { | ||||||||||
| const auto* data = static_cast<const uint8_t*>(buffer); | ||||||||||
| for (size_t offset = 0; size - offset >= 12;) { | ||||||||||
| uint32_t note[3]; | ||||||||||
| std::memcpy(note, data + offset, sizeof(note)); | ||||||||||
| const size_t namesz = note[0]; | ||||||||||
| const size_t descsz = note[1]; | ||||||||||
| if (namesz > size - offset - 12) return false; | ||||||||||
| const size_t name_end = offset + 12 + ((namesz + 3) & ~size_t{3}); | ||||||||||
| if (name_end > size || descsz > size - name_end) return false; | ||||||||||
| if (note[2] == NT_GNU_BUILD_ID && namesz >= 4 && | ||||||||||
| std::memcmp(data + offset + 12, "GNU", 3) == 0) { | ||||||||||
| *build_id = utils::buffer_to_hex(data + name_end, descsz); | ||||||||||
| return true; | ||||||||||
| } | ||||||||||
| const size_t next = name_end + ((descsz + 3) & ~size_t{3}); | ||||||||||
| if (next <= offset || next > size) return false; | ||||||||||
| offset = next; | ||||||||||
| } | ||||||||||
| return false; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| } // namespace | ||||||||||
|
|
||||||||||
|
|
||||||||||
| int GetBuildId(const std::string& path, std::string* build_id) { | ||||||||||
| static std::unordered_map<std::string, std::string> build_id_cache_; | ||||||||||
|
|
||||||||||
| // Not thread-safe; call only from the EnvList thread. | ||||||||||
| DCHECK(utils::are_threads_equal(uv_thread_self(), EnvList::Inst()->thread())); | ||||||||||
|
|
||||||||||
| Elf* e; | ||||||||||
|
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.
On the The
Suggested change
|
||||||||||
| Elf_Scn* scn = nullptr; | ||||||||||
| GElf_Shdr shdr; | ||||||||||
|
|
||||||||||
| auto it = build_id_cache_.find(path); | ||||||||||
| if (it != build_id_cache_.end()) { | ||||||||||
| *build_id = it->second; | ||||||||||
| return 0; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| int ret = 0; | ||||||||||
| if (elf_version(EV_CURRENT) == EV_NONE) { | ||||||||||
| return elf_errno(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| int fd = open(path.c_str(), O_RDONLY); | ||||||||||
| if (fd < 0) { | ||||||||||
| return -errno; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| e = elf_begin(fd, ELF_C_READ, nullptr); | ||||||||||
| if (!e) { | ||||||||||
| ret = elf_errno(); | ||||||||||
| goto error; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| build_id->clear(); | ||||||||||
| size_t shstrndx; | ||||||||||
| if (elf_getshdrstrndx(e, &shstrndx) != 0 || shstrndx == SHN_UNDEF) { | ||||||||||
| size_t phnum; | ||||||||||
| if (elf_getphdrnum(e, &phnum) != 0) { | ||||||||||
| ret = elf_errno(); | ||||||||||
| goto end_error; | ||||||||||
| } | ||||||||||
| for (size_t i = 0; i < phnum && build_id->empty(); ++i) { | ||||||||||
| GElf_Phdr phdr; | ||||||||||
| if (gelf_getphdr(e, static_cast<int>(i), &phdr) != &phdr) { | ||||||||||
| ret = elf_errno(); | ||||||||||
| goto end_error; | ||||||||||
| } | ||||||||||
| if (phdr.p_type != PT_NOTE || phdr.p_filesz > | ||||||||||
| std::numeric_limits<size_t>::max()) { | ||||||||||
| continue; | ||||||||||
| } | ||||||||||
| Elf_Data* data = elf_getdata_rawchunk( | ||||||||||
| e, phdr.p_offset, static_cast<size_t>(phdr.p_filesz), ELF_T_BYTE); | ||||||||||
| if (data) { | ||||||||||
| ParseBuildIdNotes(data->d_buf, data->d_size, build_id); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } else { | ||||||||||
| while ((scn = elf_nextscn(e, scn)) != nullptr) { | ||||||||||
| if (gelf_getshdr(scn, &shdr) != &shdr) { | ||||||||||
| ret = elf_errno(); | ||||||||||
| goto end_error; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| char* name = elf_strptr(e, shstrndx, shdr.sh_name); | ||||||||||
| if (name && strcmp(name, ".note.gnu.build-id") == 0) { | ||||||||||
| Elf_Data* data = elf_getdata(scn, nullptr); | ||||||||||
| if (data && ParseBuildIdNotes(data->d_buf, data->d_size, build_id)) { | ||||||||||
| break; | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (build_id->empty()) { | ||||||||||
| ret = UV_ENOENT; | ||||||||||
| } else { | ||||||||||
| build_id_cache_[path] = *build_id; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| end_error: | ||||||||||
| elf_end(e); | ||||||||||
|
|
||||||||||
| error: | ||||||||||
| close(fd); | ||||||||||
|
|
||||||||||
| return ret; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| } // namespace elf_utils | ||||||||||
| } // namespace nsolid | ||||||||||
| } // namespace node | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #ifndef SRC_NSOLID_NSOLID_ELF_UTILS_H_ | ||
| #define SRC_NSOLID_NSOLID_ELF_UTILS_H_ | ||
|
|
||
| #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | ||
|
|
||
| #include <string> | ||
|
|
||
| namespace node { | ||
| namespace nsolid { | ||
|
|
||
| namespace elf_utils { | ||
| int GetBuildId(const std::string& path, std::string* build_id); | ||
| } // namespace elf_utils | ||
| } // namespace nsolid | ||
| } // namespace node | ||
|
|
||
| #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | ||
|
|
||
| #endif // SRC_NSOLID_NSOLID_ELF_UTILS_H_ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| #include <node.h> | ||
| #include <v8.h> | ||
| #include <cassert> | ||
| #include <string> | ||
| #if defined(__linux__) | ||
| #include "../../../src/nsolid/nsolid_elf_utils.h" | ||
| #endif | ||
|
|
||
| using v8::FunctionCallbackInfo; | ||
| using v8::Isolate; | ||
| using v8::String; | ||
| using v8::Value; | ||
|
|
||
| static void GetBuildId(const FunctionCallbackInfo<Value>& args) { | ||
| #if defined(__linux__) | ||
| Isolate* isolate = args.GetIsolate(); | ||
| assert(args[0]->IsString()); | ||
| v8::String::Utf8Value path_utf8(isolate, args[0]); | ||
| std::string path(*path_utf8, path_utf8.length()); | ||
| std::string build_id; | ||
| int res = node::nsolid::elf_utils::GetBuildId(path, &build_id); | ||
| if (res != 0) { | ||
| return; | ||
| } | ||
|
|
||
| args.GetReturnValue().Set( | ||
| String::NewFromUtf8(isolate, build_id.c_str()).ToLocalChecked()); | ||
| #endif | ||
| } | ||
|
|
||
| NODE_MODULE_INIT(/* exports, module, context */) { | ||
| NODE_SET_METHOD(exports, "getBuildId", GetBuildId); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| 'targets': [ | ||
| { | ||
| 'target_name': 'binding', | ||
| 'sources': [ 'binding.cc' ], | ||
| 'includes': ['../common.gypi'], | ||
| 'defines': [ 'NODE_WANT_INTERNALS=1' ], | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| 'use strict'; | ||
| const common = require('../../common'); | ||
| const assert = require('assert'); | ||
| const { execFileSync } = require('child_process'); | ||
| const fs = require('fs'); | ||
| const os = require('os'); | ||
| const path = require('path'); | ||
| const process = require('process'); | ||
| const fixtures = require('../../common/fixtures'); | ||
|
|
||
| // Only run on Linux | ||
| if (process.platform !== 'linux') { | ||
| console.log('Skipping: nsolid-elf-utils only supported on Linux'); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| const bindingPath = require.resolve(`./build/${common.buildType}/binding`); | ||
| const binding = require(bindingPath); | ||
|
|
||
| const readelfOutput = execFileSync('readelf', ['-n', process.execPath], | ||
| { encoding: 'utf8' }); | ||
| const expected = readelfOutput.match(/Build ID:\s+([0-9a-f]+)/i)[1]; | ||
|
|
||
| const buildId = binding.getBuildId(process.execPath); | ||
| assert.strictEqual(buildId, | ||
| expected, | ||
| `Mismatch: addon='${buildId}', readelf='${expected}'`); | ||
|
|
||
| const fixtureHex = fixtures.readSync(['elf', 'build-id-no-sections.hex'], 'utf8'); | ||
| const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nsolid-elf-utils-')); | ||
| const fixturePath = path.join(fixtureDir, 'build-id-no-sections'); | ||
| fs.writeFileSync(fixturePath, Buffer.from(fixtureHex.replace(/\s/g, ''), 'hex')); | ||
| try { | ||
| assert.strictEqual(binding.getBuildId(fixturePath), | ||
| '00112233445566778899aabbccddeeff00112233'); | ||
| } finally { | ||
| fs.rmSync(fixtureDir, { recursive: true, force: true }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| 7f454c4602010100000000000000000002003e00010000000000000000000000 | ||
| 4000000000000000000000000000000000000000400038000100000000000000 | ||
| 0400000000000000780000000000000000000000000000000000000000000000 | ||
| 2400000000000000240000000000000004000000000000000400000014000000 | ||
| 03000000474e550000112233445566778899aabbccddeeff00112233 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🌐 Web query:
According to the ELF gABI, are note-header fieldsnamesz,descsz, andtypestored using the byte order specified byEI_DATA?💡 Result:
Yes, according to the ELF gABI, the note-header fields namesz, descsz, and type are stored using the byte order specified by EI_DATA in the ELF file header [1]. The ELF specification explicitly states that these word-sized fields (namesz, descsz, and type) must follow the byte order defined in the ELF Header's e_ident[EI_DATA] field (which specifies either little-endian via ELFDATA2LSB or big-endian via ELFDATA2MSB) [1][2]. This ensures that the note information is correctly interpreted regardless of the host machine's native endianness [1].
Citations:
🏁 Script executed:
Repository: nodesource/nsolid
Length of output: 8546
Decode note headers using the ELF file byte order.
ParseBuildIdNotescopies the rawPT_NOTEheader into host-endianuint32_tvalues. For an ELF file whoseEI_DATAdiffers from the host byte order, this can fail the bounds checks.GetBuildIdthen returnsUV_ENOENTwithout the Build ID.Pass the ELF data encoding to
ParseBuildIdNotesand decode all three fields before the bounds checks. Add a big-endian sectionlessPT_NOTEfixture.🤖 Prompt for AI Agents