-
Notifications
You must be signed in to change notification settings - Fork 14
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
feat: use objcopy
instead of LIEF
#64
Conversation
Fixes: #63 Signed-off-by: Darshan Sen <[email protected]>
@@ -95,7 +112,6 @@ describe("postject CLI", () => { | |||
console.log(err.message); | |||
} | |||
if (codesignFound) { | |||
execSync(`codesign --sign - ${filename}`); |
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.
For some reason, code signing the binary after running objcopy --add-section
on macOS causes the binary to crash:
$ ./node
[1] 5366 killed ./node
$ echo $?
137
and I can't even lldb
into it:
lldb -- ./node
(lldb) target create "./node"
Current executable set to '/Users/raisinten/Desktop/git/postject/node' (x86_64).
(lldb) run
error: Cannot allocate memory
(lldb) ^D
However, it seems to be possible to run objcopy
after code signing and not invalidate the code signature! I also tried to check if the new section was being added after the LC_CODE_SIGNATURE
section that @bnoordhuis mentioned in nodejs/node#45066 (comment) and it seems like objcopy
adds new sections at the end, even after LC_CODE_SIGNATURE
but the codesign
tool thinks that this is valid:
$ otool -l ./node | tail -n29
Load command 18
cmd LC_CODE_SIGNATURE
cmdsize 16
dataoff 84875008
datasize 663232
Load command 19
cmd LC_SEGMENT_64
cmdsize 152
segname __POSTJECT
vmaddr 0x00000001057d4000
vmsize 0x0000000000001000
fileoff 68509696
filesize 4096
maxprot 0x00000007
initprot 0x00000007
nsects 1
flags 0x0
Section
sectname __NODE_JS_CODE
segname __POSTJECT
addr 0x00000001057d4000
size 0x0000000000000014
offset 68509696
align 2^0 (1)
reloff 0
nreloc 0
flags 0x00000000
reserved1 0
reserved2 0
$ codesign -v ./node
$ echo $?
0
Any clue what's happening here?
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.
For some reason, code signing the binary after running
objcopy --add-section
on macOS causes the binary to crash
A binary with segments after LC_CODE_SIGNATURE
is not valid for code signing. There are certain requirements for a Mach-O executable being signable, usually when they're not met the codesign tool you get the error "main executable failed strict validation", not sure why it's not happening in this case.
[1] 5366 killed ./node
If you check dmesg
you can get more information on why it was killed. On macOS when an executable is killed like that immediately on start (killed, not crashing) then it is probably the kernel killing it, and there will be a message logged as to why it killed it.
I wasn't able to reproduce this case (I can see the segment is after LC_CODE_SIGNATURE
) in a quick test, but if you can reliably reproduce, check dmesg
to find more information on why it was killed.
src/api.js
Outdated
} | ||
break; | ||
execSync( | ||
`/usr/local/opt/llvm/bin/llvm-objcopy --add-section ${machoSegmentName},__${resourceName}=${resource} ${filename}` |
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.
can we just use llvm-objcopy
from $PATH
?
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.
brew install llvm
does not add it to the path
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.
hmm. this will probably not work on linux, on my system the llvm install is located under /usr/lib/llvm/14/
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.
correct, the path (even the binary name) is different on linux. I haven't implemented that part yet, will do that soon.
@RaisinTen I thought the agreement from the last call is that we would NOT do this on Postject. |
@jviotti we didn't consider a point during the meeting. I posted about it yesterday in the issue - #63 (comment). |
Refs: #64 (comment) Signed-off-by: Darshan Sen <[email protected]>
I thought the conclusion was:
cc @tony-go @mhdawson in case you can confirm whether I got it wrong or not |
Correct but I forgot to mention a point during the meeting - #63 (comment):
i.e., while testing out pkg I found that if it produces an ELF file when you're packaging an app with native dependencies on macOS, the Does this change anything? |
Yeah, that's very interesting. If so, I'm fine with the |
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.
Left some comments about the implementation on this PR, but I’m generally -1 on this change, see my comments on #63.
Another general comment on this PR, the commit message isn’t accurate for what the PR does - first it should specifically mention llvm-objcopy
, and second it should specify that this PR is macOS only.
} | ||
} | ||
break; | ||
await execFile("/usr/local/opt/llvm/bin/llvm-objcopy", [ |
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.
This path shouldn't be hardcoded, since it depends on the user's system. For example, on my system after doing brew install llvm
, I do not have that path, it says it didn't link it since there's already system binaries which it would conflict with.
The more robust option would be to try to find the binary, and also allow an option or environment variable to override the path.
@@ -95,7 +112,6 @@ describe("postject CLI", () => { | |||
console.log(err.message); | |||
} | |||
if (codesignFound) { | |||
execSync(`codesign --sign - ${filename}`); |
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.
For some reason, code signing the binary after running
objcopy --add-section
on macOS causes the binary to crash
A binary with segments after LC_CODE_SIGNATURE
is not valid for code signing. There are certain requirements for a Mach-O executable being signable, usually when they're not met the codesign tool you get the error "main executable failed strict validation", not sure why it's not happening in this case.
[1] 5366 killed ./node
If you check dmesg
you can get more information on why it was killed. On macOS when an executable is killed like that immediately on start (killed, not crashing) then it is probably the kernel killing it, and there will be a message logged as to why it killed it.
I wasn't able to reproduce this case (I can see the segment is after LC_CODE_SIGNATURE
) in a quick test, but if you can reliably reproduce, check dmesg
to find more information on why it was killed.
if (result === postject.InjectResult.kAlreadyExists) { | ||
throw new Error( | ||
`Segment and section with that name already exists: ${machoSegmentName}/${sectionName}\n` + | ||
"Use --overwrite to overwrite the existing content" | ||
); | ||
} |
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.
The changes in this PR break --overwrite
since it doesn't check if the section exists.
I believe you'd want to use llvm-objcopy --update-section
to get the overwrite functionality.
// Create the segment and mark it read-only | ||
LIEF::MachO::SegmentCommand new_segment(segment_name); | ||
new_segment.max_protection( | ||
static_cast<uint32_t>(LIEF::MachO::VM_PROTECTIONS::VM_PROT_READ)); | ||
new_segment.init_protection( | ||
static_cast<uint32_t>(LIEF::MachO::VM_PROTECTIONS::VM_PROT_READ)); | ||
new_segment.add_section(section); |
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.
This PR doesn't mark the segment as read-only, instead the segment will get the default protections, read/write/execute. Part of the security model of Postject is that the resources are injected as read-only to ensure they aren't at risk of being modified in a running executable, which the OS providing that automatically since the loaded segments are read-only and so the virtual memory pages will be read-only.
There is a --set-section-flags
option which could be used simultaneously to make the segment read-only, but trying to use it on a Mach-O executable tells me: llvm-objcopy: error: option is not supported for MachO
.
Fixes: #63
Signed-off-by: Darshan Sen [email protected]