05nelsonm / kmp-tor

Kotlin Multiplatform Library for embedding Tor into your applications
Apache License 2.0
33 stars 5 forks source link

Tor immediately shuts down #285

Closed craigraw closed 1 year ago

craigraw commented 1 year ago

I've started writing a demo JavaFX app in Java to test out kmp-tor: https://github.com/craigraw/kmp-tor-java

I've run into a problem to which the answer is not readily apparent - Tor shuts down/is destroyed immediately on startup:

Lifecycle(class=RealTorManager@1605316700, event=onCreate)
State(torState=Tor: Starting, networkState=Network: Disabled)
Action.Start
I/Starting Tor with the following settings:
----------------------------------------------------------------
ControlPort 9051
SocksPort 9050
CacheDirectory /var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/cache
ControlPortWriteToFile /var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/work/control.txt
CookieAuthFile /var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/work/control_auth_cookie
CookieAuthentication 1
DataDirectory /var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/work/data
DisableNetwork 1
DormantCanceledByStartup 1
GeoIPFile /var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/work/geoip
GeoIPv6File /var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/work/geoip6
RunAsDaemon 0
SyslogIdentityTag TorManager
__OwningControllerProcess 8796
----------------------------------------------------------------
D/Reading Process.inputStream
D/Reading Process.errorStream
D/Tor Process destroyed
D/Stopped reading Process.inputStream
D/Stopped reading Process.errorStream
State(torState=Tor: Off, networkState=Network: Disabled)
E/io.matthewnelson.kmp.tor.manager.common.exceptions.TorManagerException: Failed to read control.txt

I'd also appreciate any insight on:

  1. Whether my Java syntax of interacting with the Kotlin libraries is correct (my Kotlin knowledge is limited)
  2. The recommended approach to create a Tor-based socket. I have been using https://github.com/JesusMcCloud/netlayer/blob/master/tor/src/main/kotlin/org/berndpruenster/netlayer/tor/TorSockets.kt, which in turn relies on com.runjva.sourceforge.jsocks.protocol.SocksSocket.
05nelsonm commented 1 year ago
E/io.matthewnelson.kmp.tor.manager.common.exceptions.TorManagerException: Failed to read control.txt

Not 100% certain why it is failing immediately; there is a 10s limit set to wait for Tor to create the file REF

  1. Whether my Java syntax of interacting with the Kotlin libraries is correct (my Kotlin knowledge is limited)

Kotlin compiles it down to java byte code, so should not be an issue at all. There are some things that are a little wonky with IDEs not liking some inheritance structures, but Java interop is great. Best bet is to follow the :samples:java:javafx sample provided.

  1. The recommended approach to create a Tor-based socket. I have been using

Not necessary anymore. kmp-tor is all event based whereby things are dispatched to whatever listeners you have attached to TorManager. This is to create a buffered medium between Tor, and your application's state.

Once Tor is done bootstrapping, an AddressInfo object is dispatched to your listeners such that you are able to instantiate whatever http client you want to use, however you see fit. See HERE.

I was adamant about limiting dependencies to only what was absolutely necessary. You can utilize just a straight up java.net.Socket by passing it the resulting ProxyAddress (retrieved from the AddressInfo).

private fun createSocksSocket(proxy: ProxyAddress): Socket {
    return Socket(
        Proxy(
            Proxy.Type.Socks,
            InetSocketAddress(proxy.address.value, proxy.port.value)
        )
    )
}

In the event you modify the Socks, Http, Trans, Dns, etc ports or UnixSocket addresses, once Tor has finished reacting to what it received over the control port, another AddressInfo object will be dispatched with the changes.

05nelsonm commented 1 year ago

Oh! try applying the kotlin gradle plugin

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.8.10'
}
05nelsonm commented 1 year ago

Oh! try applying the kotlin gradle plugin

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.8.10'
}

Tried your sample with the kotlin jvm plugin. Works for me on linux x64 and macOS x64.

Edit: Also tried it w/o the plugin and it worked fine. So this seems like some issue with a configuration setting that the arm64 binaries do not like. Could you try providing an empty config (which will use default settings)?

return new TorConfig.Builder().build();
05nelsonm commented 1 year ago

Also, you're instantiating new instances of TorManager every time the button is clicked. The instance should be static, and the button click should just call torManager.startQuietly().

Think this is actually the issue that you're seeing. So, multiple clicks is dropping the last TorManager reference and setting a new instance which is spinning up a new Tor daemon with the same settings. Tor can't overwrite the control port file b/c the last TorManager instance wrote it to disk already. If you wish to use multiple instances, you can do that via the MultiInstanceManager whereby your workDir must be different to avoid such issues.

craigraw commented 1 year ago

Thanks for the PR, and the other answers - I completely missed the Java example, which is very useful!

Unfortunately, no change on this particular issue. I did a test on macOS x64 and it runs fine, so it appears to be related to the macOS arm64 binaries. After upgrading JavaFX to v18 on the sample app, and making a few changes to ensure that the arm64 binaries are installed, I managed to reproduce the issue on the Java sample app too.

Executing the extracted Tor directly results in

/var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/kmptor-j/work/.kmptor/tor
[1]    10978 killed     /var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/kmptor-j/work/.kmptor/tor

I'll keep digging to see if I can find any root cause.

craigraw commented 1 year ago

I believe this might be related to a MacOS signature issue. When I sign the tor binary with my developer certificate, it executes successfully and Tor starts as normal, instead of being immediately killed. I haven't yet managed to get the Java app to work however.

craigraw commented 1 year ago

Confirmed - I've managed to get the Java app to start by signing the tor andlibevent-2.1.7.dylib arm64 binaries. They appear to be unsigned:

❯ spctl --assess --type install --context context:primary-signature -v "/var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/work/.kmptor/libevent-2.1.7.dylib"
/var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/work/.kmptor/libevent-2.1.7.dylib: rejected
source=no usable signature
❯ spctl --assess --type install --context context:primary-signature -v "/var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/work/.kmptor/tor"
/var/folders/7y/cvqm9r6n0cgddhm921cm1zwc0000gn/T/.kmp-tor-java/work/.kmptor/tor: rejected
source=no usable signature
05nelsonm commented 1 year ago

Just checked the macosx64 binaries and they are unsigned as well. :thinking:

Looking into this.

05nelsonm commented 1 year ago

So even the expert bundles that the tor project publishes HERE are unsigned.

wget https://dist.torproject.org/torbrowser/12.5a3/tor-expert-bundle-12.5a3-macos-x86_64.tar.gz
wget https://dist.torproject.org/torbrowser/12.5a3/tor-expert-bundle-12.5a3-macos-aarch64.tar.gz
tar -xzf tor-expert-bundle-12.5a3-macos-x86_64.tar.gz
cd tor
spctl --assess --type install --context context:primary-signature -v tor
spctl --assess --type install --context context:primary-signature -v libevent-2.17.dylib
cd ..
rm -rf tor
rm -rf data
tar -xzf tor-expert-bundle-12.5a3-macos-aarch64.tar.gz
cd tor
spctl --assess --type install --context context:primary-signature -v tor
spctl --assess --type install --context context:primary-signature -v libevent-2.17.dylib

Unfortunate :cry:

Got a few q's that I hope you may be able to answer:

craigraw commented 1 year ago

To ad hoc sign binaries you can do

codesign --force --deep -s - tor
codesign --force --deep -s - libevent-2.1.7.dylib

This does however change the files.

However, it's confusing: I am able to run the tor contained in tor-expert-bundle-12.5a3-macos-aarch64.tar.gz. spctl returns the same

spctl --assess -v tor
tor: rejected
source=no usable signature

Back on kmptor, I have found the relevant error when the process is killed in the macOS Console:

AMFI: hook..execve() killing pid 12760: Attempt to execute completely unsigned code (must be at least ad-hoc signed).

This article provides more information: https://eclecticlight.co/2021/01/26/when-you-dont-have-permission-to-run-an-app-on-an-m1-mac/

Still looking into this.

05nelsonm commented 1 year ago

However, it's confusing: I am able to run the tor contained in tor-expert-bundle-12.5a3-macos-aarch64.tar.gz. spctl returns the same

Does the system check for signatures every single time they are executed on? Only thing I can think of is that you signed the files previously and the system didn't check after you replaced them with those from the expert bundle.

Trying to figure out how I might overcome this. Was banking on these binaries being reproducibly built for security purposes, verifiable by anyone by simply running the build script. Binaries from TorBrowser for macOS are signed, but they're combined x86_64 + aarch64 (they run on both architectures). So cannot yeet them from there.

Going to publish a -SNAPSHOT version of the expert bundle binaries repackaged, if you could try running those on aarch64?

craigraw commented 1 year ago

Does the system check for signatures every single time they are executed on?

Yes, every time. It's pretty mental. I can reproduce all of this reliably (so I don't think it's due to some previous action I took), but I'm completely stumped as to why the expert bundle tor works. Perhaps macOS has an exception for it?

Going to publish a -SNAPSHOT version of the expert bundle binaries repackaged, if you could try running those on aarch64?

Yes sure

05nelsonm commented 1 year ago

Alright, I published a -SNAPSHOT of kmp-tor-binary branch issue/66-repackage-expert-bundle which is just a repacking of all the expert bundles (after gpg verifying each, will move this into a script if we're successful).

If you could add to your build.gradle file the following:

and give it a try.

craigraw commented 1 year ago

Unfortunately that didn't work :( Same error as before, Attempt to execute completely unsigned code

It gets stranger though: I have verified that the tor from the expert bundle is exactly the same as the tor that is unpacked (with shasum -a 256 tor). So it appears to be something external to the file that is the culprit here. I had previously thought this to be something related to xattr, but neither of the files report anything from xattr tor. It does however look like signatures can get stripped with some kinds of compression: https://eclecticlight.co/2019/11/12/preparing-for-new-security-rules-how-signatures-can-get-stripped/.

That said, it gets even stranger - copying tor and libevent extracted from kmptor into the extracted expert bundle also results in working Tor. So there is something else about the expert bundle that is the key. I'll keep looking tomorrow.

05nelsonm commented 1 year ago

Think there is some sort of linking behavior that is being preserved by the tar archive? On Linux for the Process environment it is setting LD_LIBRARY_PATH

Could you clone this repo and try running the sample (./gradlew :samples:kotlin:javafx:run -PKMP_TARGETS="JVM"). If same error, try modifying that line to also include macos (don't use :samples:java:javafx b/c that relies on the maven dependency and won't pull in any changes below from the project).

if (installer.isLinux || installer.isMacos) {

?

EDIT: I really wish I had an M1/2 to debug this on...

EDIT2: If the above does not work using LD_LIBRARY_PATH, could also try the following

if (installer.isMacos) {
    env["DYLD_LIBRARY_PATH"] = installationDir.absolutePath
}
craigraw commented 1 year ago

Ok, I think I have found the issue. For some reason, macOS prevents calling an unsigned binary with a path, but allows it to be called without a path from the directory the binary is located in. This is what I was doing previously by accident - calling the expert bundle tor without a path specified, but specifying the full path to the extracted tor used by kmptor.

I have verified that this works:

ProcessBuilder pb = new ProcessBuilder("tor");
pb.directory(new File(System.getProperty("user.home") + "/tor"));
p = pb.start();

while this does not:

ProcessBuilder pb = new ProcessBuilder(System.getProperty("user.home") + "/tor/tor");
p = pb.start();
05nelsonm commented 1 year ago

I think that's running the tor package you have installed on your machine, not the binaries that we are extracting. I tried modifying the loader to use tor instead of the absolute path to the extracted tor file + setting the ProcessBuilder.directory, and debug info indicated that it was running an older version of tor (the version I have on my linux laptop). Running the same code on macOS x86_64 vm resulted in a file not found error (b/c tor is not installed as a package on there).

05nelsonm commented 1 year ago

~Going to yeet the binaries from TorBrowser (b/c they're signed)~ Going to codesign macos binaries and publish another -SNAPSHOT if you could test them out on your M1/2.

craigraw commented 1 year ago

I think that's running the tor package you have installed on your machine

You're right - seems obvious in retrospect!

Going to codesign macos binaries

Great. Note it doesn't look like the adhoc signing I mentioned above helps much. A developer certificate is needed for codesign to create distributable binaries afaik.

05nelsonm commented 1 year ago

Note it doesn't look like the adhoc signing I mentioned above helps much. A developer certificate is needed for codesign to create distributable binaries afaik.

Well shit, lol. I just published a 4.7.13-3-SNAPSHOT with the adhoc signing. Could you run your sample using steps outlined in https://github.com/05nelsonm/kmp-tor/issues/285#issuecomment-1470493580 but with the new version just to check?

craigraw commented 1 year ago

It worked! It looks like the adhoc signing is enough, at least for my machine.

That said, it's probably better to have a developer cert signed binary, since spctl still rejects it, so I'm not feeling that confident yet.

05nelsonm commented 1 year ago

That said, it's probably better to have a developer cert signed binary

Definitely. Will ask devs if they plan to codesign the expert package binaries or not in order to figure out next steps (build program that extracts tor from TorBrowser, or uses the expert package which I much prefer).

Can publish a new release (4.7.13-2) using developer cert signed binaries from TorBrowser macos in the mean time if that works for you?

craigraw commented 1 year ago

Can publish a new release (4.7.13-2) using developer cert signed binaries from TorBrowser macos in the mean time if that works for you?

Yes, that's probably a good step for now. Is there a particular reason you prefer the expert bundle binaries - simply more platform targetted?

05nelsonm commented 1 year ago

Yes, that's probably a good step for now.

Will publish a new -2 version with the developer codesigned binaries from TorBrowser macOS and get to work on figuring out next steps.

simply more platform targetted?

That, and the expert bundles are just .tar.gz archives with the same directory hierarchy and file names across all publications. TorBrowser has different file names and locations of the tor files depending on the build. So, coding up an extraction and packaging script is much easier using the expert bundles.

What I'd prefer is to be able to build them from source, as I have been working on RBM scripts for tor-browser-build to also do reproducible builds for iOS, tvOS, watchOS. Because signing the binaries breaks reproducibility, I need to look into a few different avenues before coding anything up.

What are your thoughts on using binaries signed by me, instead of the tor project? I would only do this if I am able to create a binary signature stripping tool such that kmp-tor-binary library consumers are able to verify the stripped binaries match those that they have reproducibly built on their own (by running the build_binaries.sh script as noted in the kmp-tor-binary/README.md).

05nelsonm commented 1 year ago

Alright @craigraw . I published a -4-SNAPSHOT using the developer signed binaries from TorBrowser. If you could check 1 last time for me on aarch64, would be much appreciated.

I've already run it on macOS x86_64, but just want to be certain things are in working order before bumping release versions to 4.7.13-2.

craigraw commented 1 year ago

If you could check 1 last time for me on aarch64, would be much appreciated.

Confirmed working!

What are your thoughts on using binaries signed by me, instead of the tor project?

I'd be happy with this - in fact it would be my ideal outcome I think, since I take your points around the expert vs TorBrowser bundles. I was considering signing the macOS binaries myself as a custom install, but I'd be much happier if you maintained this.

Andrew Chow created an open source Apple code signing utility at https://github.com/achow101/signapple. I haven't tried it, but it was specifically created to manage Bitcoin Core code signing duties afaik, with the need for reproducible builds. The idea is that detached signatures are released which someone trying to reproduce the build can use to create the final signed binary (without actually signing it). See https://github.com/bitcoin/bitcoin/blob/master/doc/release-process.md#codesigning

05nelsonm commented 1 year ago

Release 4.7.13-2-1.4.0 has been published which resolves things for the time being.

Further work on the binary build/verification process to resolve the underlying issues found in this ticket can be followed via kmp-tor-binary #66, which will be completed before the next tor version bump from 0.4.7.13.

Closing this ticket, and picking things up in kmp-tor-binary-#66

Thanks for helping figure this thing out @craigraw !