Skip to content

Instantly share code, notes, and snippets.

@billywhizz
Last active January 24, 2025 21:57
Show Gist options
  • Save billywhizz/0b01bc20741aa7977bbb0299f2cb8ad7 to your computer and use it in GitHub Desktop.
Save billywhizz/0b01bc20741aa7977bbb0299f2cb8ad7 to your computer and use it in GitHub Desktop.
node SEA loading node addon from memory experiment

see the changes

caveats

  • this is just PoC code. not meant to represent a desired api. just enough to get things to work on linux.
  • this technique will only work on linux but there are possibly other techniques that would work on macos and windows. more to follow.
  • it will only work if procfs is mounted. dlopen is called with the path /proc/self/${fd} with the fd from memfd_create
  • you will still have to deal with the issue of any external dependencies of the node addons you want to embed in the SEA
    • if static linking the addons against those dependencies is an option you can do that
  • it would be much nicer if we could static link addons and their dependencies into a statically linked SEA binary, but this would require re-linking node

try it out

cd sea_legs
./demo
  • use latest node as the changes are based on main
nvm install v23.6.1

rebuild the addon and run the demo

cd sea_legs
node-gyp rebuild
./sea.sh
./demo

if you want to verify the library is not being loaded from disk you can install strace

sudo apt install -y strace

and run the demo to see the files it opens

cd sea_legs
strace -e openat ./demo

you should see output like this

openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/ssl/openssl.cnf", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/version_signature", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/proc/self/cgroup", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/sys/fs/cgroup/user/memory.max", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/sys/fs/cgroup/user/memory.high", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/proc/meminfo", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY) = 21
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY) = 21
openat(AT_FDCWD, "/workspace/node/sea_legs/demo", O_RDONLY) = 21
openat(AT_FDCWD, "/proc/self/fd/21", O_RDONLY|O_CLOEXEC) = 22
openat(AT_FDCWD, "/dev/pts/1", O_RDWR|O_NOCTTY|O_CLOEXEC) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/null", O_RDONLY|O_CLOEXEC) = 22
openat(AT_FDCWD, "/dev/pts/1", O_RDWR|O_NOCTTY|O_CLOEXEC) = -1 EACCES (Permission denied)
1806
+++ exited with 0 +++

as opposed to what you would see with node.js

cd sea_legs
strace -e openat ../out/Release/node demo.js
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/ssl/openssl.cnf", O_RDONLY) = 3
openat(AT_FDCWD, "/proc/version_signature", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/proc/self/cgroup", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/sys/fs/cgroup/user/memory.max", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/sys/fs/cgroup/user/memory.high", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/proc/meminfo", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY) = 21
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY) = 21
openat(AT_FDCWD, "/workspace/node/out/Release/node", O_RDONLY) = 21
openat(AT_FDCWD, "/workspace/node/sea_legs/package.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/workspace/node/package.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/workspace/package.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/workspace/node/sea_legs/package.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/workspace/node/package.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/workspace/package.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/workspace/node/sea_legs/demo.js", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/workspace/node/sea_legs/package.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/workspace/node/package.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/workspace/package.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/workspace/node/sea_legs/build/Release/demo.node", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/dev/pts/1", O_RDWR|O_NOCTTY|O_CLOEXEC) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/null", O_RDONLY|O_CLOEXEC) = 21
openat(AT_FDCWD, "/dev/pts/1", O_RDWR|O_NOCTTY|O_CLOEXEC) = -1 EACCES (Permission denied)
1833
+++ exited with 0 +++
@billywhizz
Copy link
Author

more info on macos here

@billywhizz
Copy link
Author

info on how to do it on windoze

@billywhizz
Copy link
Author

more on macos. the malware literature is great for learning. 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment