Closed kzaher closed 5 years ago
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
How is an issue like this closed without being addressed?
Wtf?
How is this closed?
The binary is probably from this project? https://github.com/tekezo/Karabiner-VirtualHIDDevice
How did you determine that keys are logged? Were you able to detect anything being sent over the internet? I have Little Snitch installed on my machine.
Of course, you can build Karabiner-VirtualHIDDevice from source code with replacing CODESIGN_IDENTITY
with yours.
However, macOS requires a special signing key for the kernel extensions. Without it, you cannot load the kernel extension with SIP. https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/KernelExtensions/KernelExtensions.html
Thus, normally you have to upgrade the Developer ID Certificate to be able to sign the kext. This is why Karabiner-Elements embed the signed kernel extension binary.
Hi @tekezo ,
First of all I would say that I appreciate all of your giant effort to open source this tool. I really loved to use it, and it was almost indispensable to me.
This is also the reason why this is so frustrating. Because of this I can't use this project anymore. The risk is simply too high.
There are two ways of distributing the code:
What went wrong: When a user is trying to build the binary from source files, because they care about security, you are presenting the built from sources progress to the user. Even though the process fails because of signing issues, you ignore the entire build process and just use your home built and signed kext from somewhere and build the result dmg file successfully. This gives the user an impression that everything went smooth, when the expected action actually didn't happen.
Why is this problematic:
However, macOS requires a special signing key for the kernel extensions. Without it, you cannot load the kernel extension with SIP.
I understand why you need to sign the kext. That doesn't mean that you should attach a signed kext binary and commit it as a part of source code and use it as a fallback.
Thus, normally you have to upgrade the Developer ID Certificate to be able to sign the kext. This is why Karabiner-Elements embed the signed kernel extension binary.
Like I said, I think that you are aware that Apple won't be giving those kext signing keys to developers for personal use, the risk for Apple would be too high.
Why am I so frustrated with this:
How to remedy this so that everyone is happy:
What not to do:
@kzaher I have the same opinion of yours.
But is your proposal possible?
Create reproducible kext build.
You can do use this repository. https://github.com/tekezo/Karabiner-VirtualHIDDevice
Create a kext signature for every release of Xcode that is supported (just supporting the last version of Xcode is more then sufficient for probably 99% of people).
How we can do it?
Generally, the file contents (== sha1 hash) of Contents/MacOS/VirtualHIDDevice
is differ by each build.
@tekezo
But is your proposal possible?
I'm not a compiler expert but it seems like it should be possible. Debian is trying to make their builds reproducible and they have way more code than this project.
I've tried to investigate a bit what is happening and there is a couple of sources of non determinism in your setup.
xcodebuild
that uses derived data as part of the compilation process. This won't work.*.o
files from the derived data dir to link the result file. For some reason this also fails miserably. Not sure what is linker pulling from there, maybe changed build.db
, who knows..o files are deterministic as far as I can tell. The problem is in the linker step. If you play around with linker you can get builds with stable shasum
on a single machine. I can not vouch that you would get the same result on multiple machines with these changes, or that you would need to standardize something additional, but I think I got stable linker step also on my computer. (As far as I can tell generating .o files is already stable).
I was able to get the reproducible builds on the same machine in this way. Just run your normal build and then extract all o files:
into a separate directory that all users have write access to. Like /var/tmp/Repro
. Changing the path will change the shasum
. Generate a VirtualHIDDevice.LinkFileList
and put it in the /var/tmp/Repro
directory with the following content:
/var/tmp/Repro/VirtualHIDKeyboard.o
/var/tmp/Repro/VirtualHIDPointing.o
/var/tmp/Repro/VirtualHIDRoot.o
/var/tmp/Repro/UserClient.o
/var/tmp/Repro/VirtualHIDDevice_info.o
Xcode path is /Application/Xcode.app
.
Running this command:
rm /var/tmp/Result/VirtualHIDDevice || true && touch /var/tmp/Result/VirtualHIDDevice && /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -arch x86_64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk -filelist /var/tmp/Repro/VirtualHIDDevice.LinkFileList -mmacosx-version-min=10.12 -stdlib=libc++ -Xlinker -kext -nostdlib -lkmodc++ -lkmod -lcc_kext -Xlinker -o /var/tmp/Result/VirtualHIDDevice && shasum /var/tmp/Result/VirtualHIDDevice
gives me a constant shasum
for /var/tmp/Result/VirtualHIDDevice
on a single machine.
How did I discover this? You can modify src/Makefile
in the following way:
all:
../scripts/setversion.sh
rm -rf build_now
xcodebuild -scheme VirtualHIDDevice -configuration $(CONFIGURATION) -derivedDataPath build_locally_here build
to get a local derived data inside the git repo. Remove all of the .gitignore files you have. You have at least two. One in the root and in src
, and use git diff to investigate the differences between builds. Read the full linker command and work backwards from there. Like I said, I think you only need to fix the linker step, and I've already helped you as much as my time allowed me to.
I hope this information will get you closer to some reasonable solution, but I'm not a compiler expert and you'll need to probably find one.
I would really want this to work and for you to find some solution because I really love your product, but even if there isn't an obvious solution I think you should stop doing what you are doing now and stop doing fallback on compiled signed binary.
@kzaher
Thank you for investigation!
I think shasum /var/tmp/Result/VirtualHIDDevice
guarantees the not signed binary was produced the source code.
But is it ensure the distributed binary is safe?
(Could you tell me the detail of relation?)
I think that kext contains two files. Binary and plist. I was trying to explain how it might be possible to create a signed kext without you needing to distribute a binary.
If the identical binary can be built by the user (per build environment), then you can just attach a correct signature you've made when you have been signing the binary locally.
As far as this link explains, seems like only content matters.
Wait, are you using Karabiner-VirtualHIDDevice
just to emit the keystrokes? It seems like you can detect then through IOHIDDeviceRegisterInputReportCallback
.
Wait, are you using Karabiner-VirtualHIDDevice just to emit the keystrokes?
Yes. Please read the documentation about the data flow. https://github.com/tekezo/Karabiner-Elements/blob/master/docs/DEVELOPMENT.md#core-processes
Thus, Karabiner-VirtualHIDDevice does not observe any input events. But, as you know, if the kernel extension is malformed, the kext do various attacks. So we should prove the distributed kernel extension is clean.
As my investigation, codesign
modifies the binary and there are no reliable way to strip the sign.
(== We cannot get the original unsigned binary from signed binary.)
There are some tools that strips the signature, but these tools are not enough to revert the codesign
modification. (The file hash is not same signed binary and signature stripped binary.)
https://stackoverflow.com/questions/7500381/bug-in-codesign-remove-signature-feature
So, we have to take a different approach. I guess we can do by the following steps.
codesign
in Travis CI.
(I'll pass my certificate to Travis CI in order to be able to sign with valid signature.)The build process is complete automated and there is not a chance to interfere by malicious human. (There is an assumption that we can trust Travis CI.)
@tekezo
About proving the distributed binary
Again, you shouldn't be distributing binary in the source code and fallback on it when the user fails to codesign:
Again, why:
The build process is complete automated and there is not a chance to interfere by malicious human.
I think you are missing the point completely. It doesn't matter how to binary is generated. I was never saying how to prove your binary is correct, but how to let users build their own binary (reproducible build) for which you can just provide the signature).
As my investigation, codesign modifies the binary and there are no reliable way to strip the sign. (== We cannot get the original unsigned binary from signed binary.)
I have no idea how did you investigate this. The goal isn't stripping the signature, but replacing the signature with your own for the same binary. Seems there are two parts of mach-o you need to change https://github.com/steakknife/unsign/blob/master/unsign.c
I'm really not convinced at the moment that you actually need a kext to send keystrokes. https://stackoverflow.com/questions/8027146/sending-keystroke-events-to-osx
how to let users build their own binary (reproducible build) for which you can just provide the signature).
How do I prove the signature which I provided is correct?
I think it's a macOS SIP issue and third party including me cannot provide any way to avoid the distributing signed kext binary (== including signed kext binary into the source code).
Disabling SIP (enable --without kext) is a solution, but it is highly not recommended.
I'm really not convinced at the moment that you actually need a kext to send keystrokes.
macOS provides several way to post input events,but it is not enough except virtual input device.
How do I prove the signature which I provided is correct?
Additional description:
If I have malice, I'll put the right build signature but replace the signed binary with malformed binary as you said.
like somebody joining your github organization and publishing
Karabiner-Elements requires the root privillege to install and run. So, if the distributed binary is malformed, the attacker can anything without the malformed kext. To avoid the possibility, I'm restricting Karabiner-Elements core repository collaborator (current, only me)
There shouldn’t be any binary inside the repo, so you or anyone else can’t put a malformed binary.
Even if you think that only you have access, that is only your opinion. One can only prove something has been compromised after the fact.
Why are you using the root priviledge kext anyway? You have the accessibility API to post keyboard events. I’ve tested it and it seems to work. https://stackoverflow.com/questions/8027146/sending-keystroke-events-to-osx
Hmm, how do you believe the distributed binary is built from the source code in other projects such as VirtualBox? (If you also doubt other projects, it is consistent..., but if there are some difference, please tell me.)
Why are you using the root priviledge kext anyway?
CGEventPost does not support some keys as far as I investigated. https://github.com/tekezo/Karabiner-Elements/blob/master/docs/DEVELOPMENT.md#cgeventpost
Hmm, how do you believe the distributed binary is built from the source code in other projects such as VirtualBox? (If you also doubt other projects, it is consistent..., but if there are some difference, please tell me.)
Well I guess it's game theory. I think you are already well aware of these reasons.
CGEventPost does not support some keys as far as I investigated.
Why can't you just fallback to CGEventPost
if the kext is not present?
Why do you insist on distributing privately built binary in your source code? This is a huge red flag.
If the user can't sign the build, it should fail and give the user message he needs to get a signing certificate or use a prebuilt binary from some location.
Why do you insist on distributing privately built binary in your source code?
It's for users who want to modify Karabiner-Elements.
For me, the built binary is not reasonable since I can sign with valid certificate. As you said, the binary should be removed from repository.
But I assume other people do not get kext certificate and there is not a guarantee user can get the certificate from Apple. Other people can build Karabiner-Elements from source code without any certification except kext. So, I'm adopting the current source tree to keep being able to modify Karabiner-Elements by anyone.
Why can't you just fallback to CGEventPost if the kext is not present?
We can do it but I don't have enough time to maintain the fallback code.
Other people can build Karabiner-Elements from source code without any certification except kext.
If you want to be able to allow maintainers to use your built kext, just remove the kext binary from the source code and attach it to release page as binary. They can just copy the binary themselves and put it to correct location if they want to do it.
This is transparent and right way IMHO with no nasty surprises like I've had.
We can do it but I don't have enough time to maintain the fallback code.
Is it possible for you to maybe just hack some code that does uses CGEventPost
and post a commit or point me where I can add CGEventPost
calls as a fallback so I don't need the kext extension? I really love this project and I can maintain my own version of Karabiner-Elements without the kext. This is necessary for me to be able to use this project in commercial environment. At the end of the day it doesn't really matter what I think or who I trust, but my employers security team also.
Is it possible for you to maybe just hack some code that does uses CGEventPost and post a commit or point me
I think you can do it by modify key_event_dispatcher.hpp.
In the above code, convert usage_page to cg_key_code and post it.
Old Karabiner-Elements has CGEvent support.
You can copy the code which convert key_code
to cg_key_code
.
You can convert usage_page to key_code by make_key_code
.
https://github.com/tekezo/Karabiner-Elements/blob/bd8c7af4f078746581d30a534fee1c8db581871a/src/share/types.hpp#L424
And there are some kext checks which you disable them.
They can just copy the binary themselves and put it to correct location if they want to do it.
This way is not strongify security because the distributed binary has not any proof as we discussed in above. I think your surprises was caused by the poor documentation of Karabiner-Elements about the build process with pre-built binary. So, I should write a clear explanation about it.
I'll update the how to build the article soon. https://github.com/tekezo/Karabiner-Elements/blob/master/README.md#how-to-build
I think you can do it by modify key_event_dispatcher.hpp.
Thanks a lot. I'll try to make it work without the kext.
This way is not strongify security because the distributed binary has not any proof as we discussed in above.
I was surprised because I was not building it from source which exists in the repository, but a different binary was slipped in. If you don't delete the binary from the repository you are still doing the wrong thing IMHO because users might run make without reading your instructions.
I'm not sure why do you persist on distributing a built binary in your repository. The correct action is to download the binary from some other prebuilt place. In this way there can be no misunderstanding.
You have ~30ish contributors and probably 20-30k users. IMHO it would be more important to inform the users what they are actually doing because that might be not allowed in a lot of companies and they could get in a lot of trouble with the current build process.
I updated the document. https://github.com/tekezo/Karabiner-Elements/#how-to-build
I also updated Makefile
to avoid building a package without the document.
https://github.com/tekezo/Karabiner-Elements/blob/master/Makefile
it would be more important to inform the users what they are actually doing
I think so too. I maintain the code and the docs as necessary.
The correct action is to download the binary from some other prebuilt place.
In that case, Karabiner-VirtualHIDDevice binary will be downloaded from https://pqrs.org or https://github.com, I think it's no essential difference maintain the binary into the source tree. (And manual download causes other problems such as version mismatch, wrong file placing, etc.)
Please help me to confirm is this true.
https://github.com/tekezo/Karabiner-Elements/blob/master/src/vendor/Karabiner-VirtualHIDDevice/dist/org.pqrs.driver.Karabiner.VirtualHIDDevice.v061000.kext/Contents/MacOS/VirtualHIDDevice
Karabiner-Elements-VERSION.dmg
by default your attached binary (not build from the source will be used), because only you have the sigining key!!!