Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,12 @@
}, {
'use_openssl_def%': 0,
}],
[ 'OS=="linux"', {
'nsolid_sources': [
'src/nsolid/nsolid_elf_utils.cc',
'src/nsolid/nsolid_elf_utils.h',
]
}],
],
},

Expand Down
10 changes: 9 additions & 1 deletion node.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,19 @@
[ 'OS=="sunos"', {
'ldflags': [ '-Wl,-M,/usr/lib/ld/map.noexstk' ],
}],
[ '(OS=="linux" and not nsolid_use_librt) or OS=="openharmony"', {
[ 'OS=="openharmony"', {
'libraries!': [
'-lrt'
],
}],
[ 'OS=="linux"', {
'libraries': [ '-lelf' ],
'conditions': [
[ 'not nsolid_use_librt', {
'libraries!': [ '-lrt' ],
}]
]
}],
[ 'OS in "freebsd linux openharmony"', {
'ldflags': [ '-Wl,-z,relro',
'-Wl,-z,now' ]
Expand Down
97 changes: 97 additions & 0 deletions src/nsolid/nsolid_elf_utils.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "nsolid_elf_utils.h"
#include "nsolid_util.h"

#include <fcntl.h>
#include <libelf.h>
#include <gelf.h>
#include <unistd.h>
#include <cstring>
#include <map>
#include "uv.h"

namespace node {
namespace nsolid {
namespace elf_utils {


int GetBuildId(const std::string& path, std::string* build_id) {
static std::map<std::string, std::string> build_id_cache_;

Elf* e;
Elf_Scn* scn = nullptr;
GElf_Shdr shdr;

if (build_id_cache_.find(path) != build_id_cache_.end()) {
*build_id = build_id_cache_[path];
return 0;
}

int ret;

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;
}

size_t shstrndx;
if (elf_getshdrstrndx(e, &shstrndx) != 0) {
ret = elf_errno();
goto end_error;
}

*build_id = std::string("");
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 && data->d_size >= 16) {
// ELF Note header: namesz(4), descsz(4), type(4) + name padding
// Compute offset to build-id properly
uint32_t* note = reinterpret_cast<uint32_t*>(data->d_buf);
uint32_t namesz = note[0];
uint32_t descsz = note[1];
// Name starts at offset 12
// Descriptor (build-id) starts at next aligned offset
size_t name_end = 12 + ((namesz + 3) & ~3);
uint8_t* id = reinterpret_cast<uint8_t*>(data->d_buf) + name_end;
*build_id = utils::buffer_to_hex(id, descsz);
break;
}
}
}
Comment on lines +53 to +77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential buffer overflow risk in ELF note parsing.

The code assumes the ELF note structure without validating that the computed offsets don't exceed the buffer bounds. While data->d_size >= 16 checks for minimum size, it doesn't ensure that name_end + descsz doesn't exceed data->d_size.

Add bounds checking before accessing the build-id data:

   *build_id = std::string("");
   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 && data->d_size >= 16) {
         // ELF Note header: namesz(4), descsz(4), type(4) + name padding
         // Compute offset to build-id properly
         uint32_t* note = reinterpret_cast<uint32_t*>(data->d_buf);
         uint32_t namesz = note[0];
         uint32_t descsz = note[1];
         // Name starts at offset 12
         // Descriptor (build-id) starts at next aligned offset
         size_t name_end = 12 + ((namesz + 3) & ~3);
+        // Ensure we don't read beyond the buffer
+        if (name_end + descsz > data->d_size) {
+          ret = ELF_E_DATA;
+          goto end_error;
+        }
         uint8_t* id = reinterpret_cast<uint8_t*>(data->d_buf) + name_end;
         *build_id = utils::buffer_to_hex(id, descsz);
         break;
       }
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
*build_id = std::string("");
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 && data->d_size >= 16) {
// ELF Note header: namesz(4), descsz(4), type(4) + name padding
// Compute offset to build-id properly
uint32_t* note = reinterpret_cast<uint32_t*>(data->d_buf);
uint32_t namesz = note[0];
uint32_t descsz = note[1];
// Name starts at offset 12
// Descriptor (build-id) starts at next aligned offset
size_t name_end = 12 + ((namesz + 3) & ~3);
uint8_t* id = reinterpret_cast<uint8_t*>(data->d_buf) + name_end;
*build_id = utils::buffer_to_hex(id, descsz);
break;
}
}
}
*build_id = std::string("");
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 && data->d_size >= 16) {
// ELF Note header: namesz(4), descsz(4), type(4) + name padding
// Compute offset to build-id properly
uint32_t* note = reinterpret_cast<uint32_t*>(data->d_buf);
uint32_t namesz = note[0];
uint32_t descsz = note[1];
// Name starts at offset 12
// Descriptor (build-id) starts at next aligned offset
size_t name_end = 12 + ((namesz + 3) & ~3);
// Ensure we don't read beyond the buffer
if (name_end + descsz > data->d_size) {
ret = ELF_E_DATA;
goto end_error;
}
uint8_t* id = reinterpret_cast<uint8_t*>(data->d_buf) + name_end;
*build_id = utils::buffer_to_hex(id, descsz);
break;
}
}
}
🤖 Prompt for AI Agents
In src/nsolid/nsolid_elf_utils.cc around lines 53 to 77, the ELF note parsing
computes name_end and then reads descsz bytes without verifying the computed
offsets fit inside data->d_size; add explicit bounds checks before accessing the
build-id: validate data and data->d_buf are non-null, cast namesz and descsz to
size_t and check namesz/descsz are reasonable (e.g. not absurdly large), compute
name_end = 12 + ((namesz + 3) & ~3) using size_t and ensure name_end <=
data->d_size and name_end + descsz <= data->d_size (also guard against overflow
when adding), and only then set *build_id from the id pointer; on failure, skip
this note or set an error/continue instead of reading out-of-bounds.


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

19 changes: 19 additions & 0 deletions src/nsolid/nsolid_elf_utils.h
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_
33 changes: 33 additions & 0 deletions test/addons/nsolid-elf-utils/binding.cc
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);
}
10 changes: 10 additions & 0 deletions test/addons/nsolid-elf-utils/binding.gyp
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' ],
}
]
}
23 changes: 23 additions & 0 deletions test/addons/nsolid-elf-utils/nsolid-elf-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const { execSync } = require('child_process');
const process = require('process');

// 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 expected =
execSync(`readelf -n ${process.execPath} | awk '/Build ID/ { print $3 }'`,
{ encoding: 'utf8' }).trim();

const buildId = binding.getBuildId(process.execPath);
assert.strictEqual(buildId,
expected,
`Mismatch: addon='${buildId}', readelf='${expected}'`);
Loading