Skip to content
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

interpreter: loading libcrypto in an unsafe way #12214

Closed
will opened this issue Jul 7, 2022 · 49 comments · Fixed by #13069
Closed

interpreter: loading libcrypto in an unsafe way #12214

will opened this issue Jul 7, 2022 · 49 comments · Fixed by #13069

Comments

@will
Copy link
Contributor

will commented Jul 7, 2022

On https://github.com/crunchydata/bridge-cli on cafe7b977599bff94035d870129daf40912a1851 and

../crystal/bin/crystal --version
Using compiled compiler at /Users/will/code/crystal/.build/crystal
Crystal 1.5.0 [994c70b10] (2022-07-06)

LLVM: 13.0.1
Default target: x86_64-apple-macosx

Normal crystal builds and runs the main file fine

~/c/cb (main)> ../crystal/bin/crystal src/cli.cr version
Using compiled compiler at /Users/will/code/crystal/.build/crystal
cb v2.2.0-dev-unrelease (202207072028)

but crystal i complains

~/c/cb (main)> ../crystal/bin/crystal i src/cli.cr version
Using compiled compiler at /Users/will/code/crystal/.build/crystal
WARNING: /Users/will/code/crystal/.build/crystal is loading libcrypto in an unsafe way
fish: Job 1, '../crystal/bin/crystal i src/cl…' terminated by signal SIGABRT (Abort)

I tried to grab a backtrace if that's at all helpful using CRYSTAL_PATH="lib:/Users/will/code/crystal/src" CRYSTAL_CONFIG_LIBRARY_PATH="/usr/local/lib" CRYSTAL_LIBRARY_PATH="/usr/local/lib" lldb ../crystal/.build/crystal i src/cli.cr

(lldb) run
Process 16064 launched: '/Users/will/code/crystal/.build/crystal' (x86_64)
WARNING: /Users/will/code/crystal/.build/crystal is loading libcrypto in an unsafe way
Process 16064 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007ff8172d700e libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
->  0x7ff8172d700e <+10>: jae    0x7ff8172d7018            ; <+20>
    0x7ff8172d7010 <+12>: movq   %rax, %rdi
    0x7ff8172d7013 <+15>: jmp    0x7ff8172d11c5            ; cerror_nocancel
    0x7ff8172d7018 <+20>: retq
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
  * frame #0: 0x00007ff8172d700e libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007ff81730d1ff libsystem_pthread.dylib`pthread_kill + 263
    frame #2: 0x00007ff817258d24 libsystem_c.dylib`abort + 123
    frame #3: 0x00007ffb2ff3cafe libssl.dylib`__report_load.cold.1 + 36
    frame #4: 0x00007ffb2ff3c80d libssl.dylib`__report_load + 427
    frame #5: 0x0000000102464d2b dyld`invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 182
    frame #6: 0x000000010248b237 dyld`invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 242
    frame #7: 0x000000010248283a dyld`invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 557
    frame #8: 0x0000000102451db3 dyld`dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const + 129
    frame #9: 0x00000001024825cb dyld`dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 179
    frame #10: 0x000000010248ad8e dyld`dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 466
    frame #11: 0x0000000102464c5e dyld`dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 144
    frame #12: 0x000000010246b1aa dyld`dyld4::PrebuiltLoader::runInitializers(dyld4::RuntimeState&) const + 30
    frame #13: 0x0000000102464dea dyld`dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&) const + 178
    frame #14: 0x0000000102464e8e dyld`dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const + 108
    frame #15: 0x0000000102473ac0 dyld`dyld4::APIs::dlopen_from(char const*, int, void*) + 592
    frame #16: 0x00000001010b3025 crystal`open_library at unix.cr:100:5 [opt]
    frame #17: 0x00000001010b32bd crystal`load_file? at unix.cr:88:14 [opt]
    frame #18: 0x00000001010b322e crystal`load_library? at loader.cr:92:22 [opt]
    frame #19: 0x00000001010b304d crystal`load_library at unix.cr:96:5 [opt]
    frame #20: 0x00000001010b2dc3 crystal`new at loader.cr:42:7 [opt]
    frame #21: 0x00000001010b2bb6 crystal`parse at unix.cr:60:7 [opt]
    frame #22: 0x0000000100db2be8 crystal`loader at context.cr:375:5 [opt]
    frame #23: 0x0000000100db28e1 crystal`c_function at context.cr:387:5 [opt]
    frame #24: 0x0000000101059ed3 crystal`compile_lib_call at compiler.cr:1874:17 [opt]
    frame #25: 0x000000010103f24f crystal`visit at compiler.cr:1755:7 [opt]
    frame #26: 0x00000001003e7a53 crystal`accept at visitor.cr:27:12 [opt]
    frame #27: 0x0000000101025b90 crystal`accept_with_wants_value at compiler.cr:3011:5 [opt]
    frame #28: 0x000000010102c9be crystal`request_value at compiler.cr:3001:5 [opt]
    frame #29: 0x000000010102a650 crystal`visit at compiler.cr:563:7 [opt]
    frame #30: 0x00000001003e73d1 crystal`accept at visitor.cr:27:12 [opt]
    frame #31: 0x0000000101025b90 crystal`accept_with_wants_value at compiler.cr:3011:5 [opt]
    frame #32: 0x000000010102c9be crystal`request_value at compiler.cr:3001:5 [opt]
    frame #33: 0x0000000101035ad2 crystal`visit at compiler.cr:1204:5 [opt]
    frame #34: 0x00000001003e75bb crystal`accept at visitor.cr:27:12 [opt]
    frame #35: 0x000000010102a5b8 crystal`visit at compiler.cr:551:7 [opt]
    frame #36: 0x00000001003e737d crystal`accept at visitor.cr:27:12 [opt]
    frame #37: 0x0000000101031a1b crystal`compile_def at compiler.cr:217:5 [opt]
    frame #38: 0x000000010105d05f crystal`create_compiled_def at compiler.cr:1970:7 [opt]
    frame #39: 0x000000010103f375 crystal`visit at compiler.cr:1768:22 [opt]
    frame #40: 0x00000001003e7a53 crystal`accept at visitor.cr:27:12 [opt]
    frame #41: 0x0000000101031a1b crystal`compile_def at compiler.cr:217:5 [opt]
    frame #42: 0x000000010105d05f crystal`create_compiled_def at compiler.cr:1970:7 [opt]
    frame #43: 0x000000010103f375 crystal`visit at compiler.cr:1768:22 [opt]
    frame #44: 0x00000001003e7a53 crystal`accept at visitor.cr:27:12 [opt]
    frame #45: 0x00000001010252bc crystal`visit at compiler.cr:432:7 [opt]
    frame #46: 0x00000001003e7329 crystal`accept at visitor.cr:27:12 [opt]
    frame #47: 0x0000000101031a1b crystal`compile_def at compiler.cr:217:5 [opt]
    frame #48: 0x0000000101030ba6 crystal`get_const_index_and_compiled_def at compiler.cr:1390:5 [opt]
    frame #49: 0x0000000101038dfd crystal`initialize_const_if_needed at compiler.cr:2497:27 [opt]
    frame #50: 0x0000000101038cb1 crystal`visit at compiler.cr:1362:17 [opt]
    frame #51: 0x00000001003e77b3 crystal`accept at visitor.cr:27:12 [opt]
    frame #52: 0x0000000101025b90 crystal`accept_with_wants_value at compiler.cr:3011:5 [opt]
    frame #53: 0x000000010102c9be crystal`request_value at compiler.cr:3001:5 [opt]
    frame #54: 0x000000010105b807 crystal`compile_call_args at compiler.cr:2139:9 [opt]
    frame #55: 0x000000010103f271 crystal`visit at compiler.cr:1764:5 [opt]
    frame #56: 0x00000001003e7a53 crystal`accept at visitor.cr:27:12 [opt]
    frame #57: 0x0000000101031a1b crystal`compile_def at compiler.cr:217:5 [opt]
    frame #58: 0x0000000101030ba6 crystal`get_const_index_and_compiled_def at compiler.cr:1390:5 [opt]
    frame #59: 0x000000010102c881 crystal`visit at compiler.cr:665:31 [opt]
    frame #60: 0x00000001003e73d1 crystal`accept at visitor.cr:27:12 [opt]
    frame #61: 0x000000010106c013 crystal`visit at compiler.cr:2915:5 [opt]
    frame #62: 0x00000001003e7e2e crystal`accept at visitor.cr:27:12 [opt]
    frame #63: 0x000000010102a5b8 crystal`visit at compiler.cr:551:7 [opt]
    frame #64: 0x00000001003e737d crystal`accept at visitor.cr:27:12 [opt]
    frame #65: 0x0000000101025b90 crystal`accept_with_wants_value at compiler.cr:3011:5 [opt]
    frame #66: 0x0000000101025b5b crystal`discard_value at compiler.cr:3005:5 [opt]
    frame #67: 0x000000010106bda1 crystal`visit at compiler.cr:2863:7 [opt]
    frame #68: 0x00000001003e7c4b crystal`accept at visitor.cr:27:12 [opt]
    frame #69: 0x000000010102a5b8 crystal`visit at compiler.cr:551:7 [opt]
    frame #70: 0x00000001003e737d crystal`accept at visitor.cr:27:12 [opt]
    frame #71: 0x000000010106c737 crystal`compile_def:closure_owner at compiler.cr:217:5 [opt]
    frame #72: 0x000000010106c1cf crystal`visit at compiler.cr:2963:5 [opt]
    frame #73: 0x00000001003e8056 crystal`accept at visitor.cr:27:12 [opt]
    frame #74: 0x000000010102a5b8 crystal`visit at compiler.cr:551:7 [opt]
    frame #75: 0x00000001003e737d crystal`accept at visitor.cr:27:12 [opt]
    frame #76: 0x000000010106c737 crystal`compile_def:closure_owner at compiler.cr:217:5 [opt]
    frame #77: 0x000000010106c1cf crystal`visit at compiler.cr:2963:5 [opt]
    frame #78: 0x00000001003e8056 crystal`accept at visitor.cr:27:12 [opt]
    frame #79: 0x000000010102a5b8 crystal`visit at compiler.cr:551:7 [opt]
    frame #80: 0x00000001003e737d crystal`accept at visitor.cr:27:12 [opt]
    frame #81: 0x0000000101023857 crystal`compile at compiler.cr:139:5 [opt]
    frame #82: 0x0000000100db4899 crystal`interpret at interpreter.cr:220:5 [opt]
    frame #83: 0x00000001012dffc5 crystal`interpret at repl.cr:146:5 [opt]
    frame #84: 0x00000001012dfcf0 crystal`interpret_and_exit_on_error at repl.cr:150:5 [opt]
    frame #85: 0x00000001012e078e crystal`run_file at repl.cr:121:5 [opt]
    frame #86: 0x000000010122e7a0 crystal`repl at repl.cr:44:7 [opt]
    frame #87: 0x0000000101229674 crystal`run at command.cr:100:7 [opt]
    frame #88: 0x000000010122904c crystal`run at command.cr:51:5 [opt]
    frame #89: 0x0000000101228fce crystal`run at command.cr:50:3 [opt]
    frame #90: 0x000000010000507b crystal`__crystal_main at crystal.cr:11:1 [opt]
    frame #91: 0x00000001001dc466 crystal`main_user_code at main.cr:115:5 [opt]
    frame #92: 0x00000001001dc3c8 crystal`main at main.cr:101:7 [opt]
    frame #93: 0x0000000100012149 crystal`main at main.cr:127:3 [opt]
    frame #94: 0x000000010245551e dyld`start + 462
(lldb)
@straight-shoota
Copy link
Member

This error message seems to indicate that an unversioned library filed of libssl was loaded and it want's to be loaded as a versioned file.
Would be helpful to know which file was loaded and why. We should probably add a debug optoin to Crystal::Loader in order to get similar information to what ldd would show for a linked program.

@will
Copy link
Contributor Author

will commented Jul 7, 2022

The code is public there, but also let me know if there is anything you want me to try out and report back

@asterite
Copy link
Member

asterite commented Jul 7, 2022

I think it's the same thing if you use the interpreter with the sample http server. I get the same thing. I think the link flags for openssl changed recently and that broke something.

@asterite
Copy link
Member

asterite commented Jul 7, 2022

Is that on an M1? Now that I'm thinking about it, I might started getting that just on M1. There's also cl-plus-ssl/cl-plus-ssl#114 (I didn't ready it yet)

@asterite
Copy link
Member

asterite commented Jul 7, 2022

This might also be the fix: cl-plus-ssl/cl-plus-ssl#115

@asterite
Copy link
Member

asterite commented Jul 7, 2022

(but to be honest I'm not sure what's going on... it seems we need to use versioned libcrypto and libssl libraries, fake them because those files don't actually exist... but I don't know how we can tell dlopen the exact path to open, hmmm...)

@will
Copy link
Contributor Author

will commented Jul 7, 2022

Is that on an M1?

I wish, still don’t have one :( This is on an intel

@cyangle

This comment was marked as off-topic.

@cyangle

This comment was marked as off-topic.

@straight-shoota
Copy link
Member

straight-shoota commented Jul 7, 2022

@cyangle That's something entirely different from libcrypto linking. Please open a separate issue. Thanks.

but I don't know how we can tell dlopen the exact path to open

@asterite Ehm, we just tell it which path we want? 🤔
via Loader#load_file.

@asterite
Copy link
Member

asterite commented Jul 8, 2022

Ehm, we just tell it which path we want? 🤔 via Loader#load_file.

Sure! How do you do that from a @[Link] annotation?

@straight-shoota
Copy link
Member

This appears to be working correctly in compiled mode, right? So the system linker probably does something to make this work. We just have to find out how we can emulate that for the interpreter's loader.

@cyangle
Copy link
Contributor

cyangle commented Jul 10, 2022

With latest bug fixes(not merged), the interpreter runs all bridge-cli tests successfully on Ubuntu 22.04. @will, You can use the interpreter in docker container or vscode dev container if you want to.

$ cr i src/cli.cr version
Using compiled compiler at /home/chao/git/personal/crystal/.build/crystal
cb v2.2.0-dev-unrelease (202207100146)

chao@2ddb6545b40d:~/git/personal/bridge-cli (main)
$ cr i spec/cb_spec.cr 
Using compiled compiler at /home/chao/git/personal/crystal/.build/crystal
.......................................................................................

Finished in 1.06 seconds
87 examples, 0 failures, 0 errors, 0 pending

@asterite
Copy link
Member

@straight-shoota @beta-ziliani Note that if we launch the interpreter as experimental, particularly on Mac OS, then running an http server with the interpreter doesn't work because of this issue.

@straight-shoota
Copy link
Member

This sheds a bit more light on this: https://developer.apple.com/forums/thread/119429 But it's still not conclusive what exactly we're doing wrong and what needs to be done to fix it.

I don't have a Mac. Can someone who observes this problem run a couple commands?
I'd like to get some information on what is concretely happening, especially with regards to library paths.

  • pkg-config information on libcrypto: pkg-config --libs --silence-errors libcrypto.
  • Ideally a directory listing of the library path with all matching libraries would be nice, too. Probably something like ls -l /usr/local/opt/openssl@1.1/lib/*crypto* (or whatever path pkg-config reports).
  • Linking information from a dynamically linked program that links libcrypto: something like crytstal build app.cr && otool -L ./app).
  • Linking information from the interpreter: CRYSTAL_INTERPRETER_LOADER_INFO=1 crystal i app.cr). Not sure if that works or the program already fails before printing the info.

@asterite
Copy link
Member

Here you go!

$ pkg-config --libs --silence-errors libcrypto
-L/opt/homebrew/Cellar/openssl@1.1/1.1.1q/lib -lcrypto
$ ls -l /opt/homebrew/Cellar/openssl@1.1/1.1.1q/lib/*crypto*
-r--r--r--  1 aryborenszweig  admin  2192864 Jul  8 08:02 /opt/homebrew/Cellar/openssl@1.1/1.1.1q/lib/libcrypto.1.1.dylib
-r--r--r--  1 aryborenszweig  admin  3850784 Jul  5 06:08 /opt/homebrew/Cellar/openssl@1.1/1.1.1q/lib/libcrypto.a
lrwxr-xr-x  1 aryborenszweig  admin       19 Jul  5 06:08 /opt/homebrew/Cellar/openssl@1.1/1.1.1q/lib/libcrypto.dylib -> libcrypto.1.1.dylib
$ otool -L ./app
./app:
  /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
  /opt/homebrew/opt/openssl@1.1/lib/libssl.1.1.dylib (compatibility version 1.1.0, current version 1.1.0)
  /opt/homebrew/opt/openssl@1.1/lib/libcrypto.1.1.dylib (compatibility version 1.1.0, current version 1.1.0)
  /opt/homebrew/opt/bdw-gc/lib/libgc.1.dylib (compatibility version 7.0.0, current version 7.1.0)
  /opt/homebrew/opt/libevent/lib/libevent-2.1.7.dylib (compatibility version 8.0.0, current version 8.1.0)
  /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)
$ CRYSTAL_INTERPRETER_LOADER_INFO=1 crystal i samples/http_server.cr
WARNING: /opt/homebrew/Cellar/crystal/HEAD-b262838_1/libexec/crystal is loading libcrypto in an unsafe way
[1]    31979 abort      CRYSTAL_INTERPRETER_LOADER_INFO=1 crystal i samples/http_server.cr

@straight-shoota
Copy link
Member

All right, so it looks like the system linker resolved the symlink from libcrypto.dylib to libcrypto.1.1.dylib. Maybe that's all that is to it...?

Can you try this patch?

diff --git i/src/compiler/crystal/loader/unix.cr w/src/compiler/crystal/loader/unix.cr
index fb26f5842..bef0e7433 100644
--- i/src/compiler/crystal/loader/unix.cr
+++ w/src/compiler/crystal/loader/unix.cr
@@ -85,6 +85,9 @@ class Crystal::Loader
   end

   def load_file?(path : String | ::Path) : Bool
+    if File.symlink?(path)
+      path = File.real_path(path)
+    end
     handle = open_library(path.to_s)
     return false unless handle

@asterite
Copy link
Member

I jus tried it. It didn't work. I added p! path to the above diff to see what paths are being checked. Here's what I got:

path # => "/usr/lib/libz.dylib"
path # => "/usr/lib/libssl.dylib"
WARNING: /Users/aryborenszweig/Projects/crystal/.build/crystal is loading libcrypto in an unsafe way

It actually seems it's dying on open_library for libssl, not libcrypto 🤔

@asterite
Copy link
Member

What's interesting is that neither of those files ("/usr/lib/libz.dylib" and "/usr/lib/libssl.dylib") exist on my machine.

@beta-ziliani
Copy link
Member

The explanation is in dlopen's man page:

When path contains a slash (i.e. a full path or a partial path) dlopen()
searches the following the following until it finds a compatible Mach-O
file: $DYLD_LIBRARY_PATH (with leaf name from path ), current working
directory (for partial paths), $DYLD_FALLBACK_LIBRARY_PATH (with leaf
name from path ).

so the path is not really taken into account except for local libs (not our case).

@straight-shoota
Copy link
Member

Oh, then according to this man page dlopen behaves quite differently on Mac than any other system.
I suppose it can be used pretty much like the -l argument of ld. Perhaps we don't need the loader's search path logic on Mac. As I understand it, we could just add all items in search_paths to $DYLD_LIBRARY_PATH and pass the libname to dlopen. And the function should do the job of finding the actual file path.

@beta-ziliani
Copy link
Member

yes, but that won't solve this problem 😭
we need to investigate why loading libssl trigger this.

@straight-shoota
Copy link
Member

I would expect this to solve exactly this problem. If we give dlopen just the name of the library we want to open and let it figure out the path itself, I'd bet that it does it in a way that works and doesn't break on loading the library.

@beta-ziliani
Copy link
Member

The problem is that the documentation already suggests that dlopen chops the directory off. I still tried, and indeed it doesn't work.

@straight-shoota
Copy link
Member

Well then I guess this becomes the quest to find a spell which convinces MacOS' dlopen to link libssl/libcrypto.

@asterite
Copy link
Member

Did you look at this? https://github.com/cl-plus-ssl/cl-plus-ssl/pull/115/files

It seems linking to some magic numbered versions should do the trick, but I don't know.

@cyangle
Copy link
Contributor

cyangle commented Oct 3, 2022

You can fix this issue by setting env var DYLD_LIBRARY_PATH to the homebrew's openssl lib path.

homebrew's crystal package depends on openssl@1.1 so you can set it like below:

export DYLD_LIBRARY_PATH=/usr/local/Cellar/openssl@1.1/1.1.1q/lib

@asterite @will

@will
Copy link
Contributor Author

will commented Oct 3, 2022

Thanks for the tip @cyangle. I am unfortunately not able to get it to work yet.

The path you gave

env DYLD_LIBRARY_PATH=/usr/local/Cellar/openssl@1.1/1.1.1q/lib crystal i src/cli.cr
WARNING: /usr/local/Cellar/crystal/HEAD-f276d70/libexec/crystal is loading libcrypto in an unsafe way
fish: Job 1, 'env DYLD_LIBRARY_PATH=/usr/loca…' terminated by signal SIGABRT (Abort)

and then I checked what the built binary was linking, which was a slightly different path but the same idea

$otool -L bin/cb
bin/cb:
	/usr/local/opt/libssh2/lib/libssh2.1.dylib (compatibility version 2.0.0, current version 2.1.0)
	/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
	/usr/local/opt/openssl@1.1/lib/libssl.1.1.dylib (compatibility version 1.1.0, current version 1.1.0)
	/usr/local/opt/openssl@1.1/lib/libcrypto.1.1.dylib (compatibility version 1.1.0, current version 1.1.0)
	/usr/local/opt/pcre/lib/libpcre.1.dylib (compatibility version 4.0.0, current version 4.13.0)
	/usr/local/opt/bdw-gc/lib/libgc.1.dylib (compatibility version 6.0.0, current version 6.4.0)
	/usr/local/opt/libevent/lib/libevent-2.1.7.dylib (compatibility version 8.0.0, current version 8.1.0)
	/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)



$ env DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib crystal i src/cli.cr
WARNING: /usr/local/Cellar/crystal/HEAD-f276d70/libexec/crystal is loading libcrypto in an unsafe way
fish: Job 1, 'env DYLD_LIBRARY_PATH=/usr/loca…' terminated by signal SIGABRT (Abort)

@cyangle
Copy link
Contributor

cyangle commented Oct 3, 2022

@will I'm using locally built crystal.

$ otool -L .build/crystal
.build/crystal:
        /usr/local/opt/llvm@14/lib/libLLVM.dylib (compatibility version 1.0.0, current version 14.0.6)
        /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1300.32.0)
        /usr/local/opt/pcre/lib/libpcre.1.dylib (compatibility version 4.0.0, current version 4.13.0)
        /usr/local/opt/bdw-gc/lib/libgc.1.dylib (compatibility version 7.0.0, current version 7.1.0)
        /usr/local/opt/libevent/lib/libevent-2.1.7.dylib (compatibility version 8.0.0, current version 8.1.0)
        /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
        /usr/lib/libffi.dylib (compatibility version 1.0.0, current version 30.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)

@beta-ziliani
Copy link
Member

From BigSur onwards,I found that changing DYLD_* properties doesn't work out of the box, it needs special permissions: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-dyld-environment-variables?language=objc

@beta-ziliani
Copy link
Member

OK, I got it working with two changes. Add the lib path of the relevant version of ssl and crypto libs:

export CRYSTAL_LIBRARY_PATH="/usr/local/opt/openssl@3/lib"

Added specific version to linker in lib_crypto.cr and lib_ssl.cr (with X ∈ {crypto, ssl}):

{% if flag?(:win32) %}
  ...
{% elsif flag?(:darwin) %}
  @[Link("X.3")]
{% else %}
  ...

@cyangle
Copy link
Contributor

cyangle commented Oct 3, 2022

I can reproduce the loader error with HEAD version of crystal:

$ crystal i src/cli.cr
WARNING: /usr/local/Cellar/crystal/HEAD-fd88012_1/libexec/crystal is loading libcrypto in an unsafe way
Abort trap: 6

And I got a different error with the env var:

$ env DYLD_LIBRARY_PATH=/usr/local/Cellar/openssl@1.1/1.1.1q/lib crystal i src/cli.cr
In /usr/local/Cellar/crystal/HEAD-fd88012_1/share/crystal/src/log/main.cr:36:21

 36 | private Top = Log.for("")
                        ^--



In /usr/local/Cellar/crystal/HEAD-fd88012_1/share/crystal/src/log/main.cr:36:21

 36 | private Top = Log.for("")
                        ^--
Error: compiling Log.for("")
.....
Error: compiling self >= arg0


In /usr/local/Cellar/crystal/HEAD-fd88012_1/share/crystal/src/class.cr:74:11

 74 | other._lte(self)
            ^---



In /usr/local/Cellar/crystal/HEAD-fd88012_1/share/crystal/src/class.cr:74:11

 74 | other._lte(self)
            ^---
Error: BUG: missing upcast_distinct from DB::PoolResourceLost(DB::Connection).class to DB::PoolResourceLost(DB::Connection)+.class (Crystal::GenericClassInstanceMetaclassType to Crystal::VirtualMetaclassType)

@beta-ziliani
Copy link
Member

Yes, by "got it working" I mean just the library issue. But I also got these other (unrelated) issues.

@straight-shoota
Copy link
Member

straight-shoota commented Oct 4, 2022

@cyangle Your follow-up error is another unrelated issue. I created #12561 for that.
Please try to keep the discussion here focused on loader issues with libcrypto/ssl.

@beta-ziliani
Copy link
Member

beta-ziliani commented Oct 4, 2022

In order to fix it for real, we need to somehow get the version number and plug it in the name of the library. This line gets me what I need in bash, but I don't know how to plug this into the ldflags:

CRYSTAL_CRYPTO_CFG=`pkg-config --libs --silence-errors libcrypto` && CRYSTAL_CRYPTO_VER=`sed 's|.*@\([^/]*\).*|\1|' <<< "$CRYSTAL_CRYPTO_CFG"` && echo $CRYSTAL_CRYPTO_CFG.$CRYSTAL_CRYPTO_VER

outputs

-L/usr/local/Cellar/openssl@3/3.0.5/lib -lcrypto.3

@straight-shoota
Copy link
Member

@beta-ziliani What's $CRYSTAL_TMP_CRYPTO in that script?

@beta-ziliani
Copy link
Member

Oops! updated. What that scripts attempts to do: store the output of pkg-config in CRYSTAL_CRYPTO_CFG, take the version of the library from there, and place it after the -lcrypto in that same output.

@straight-shoota
Copy link
Member

straight-shoota commented Oct 4, 2022

So basically it's just pkg-config --libs --silence-errors libcrypto | sed 's|.*@\([^/]*\).*|\0.\1|', right?

@beta-ziliani
Copy link
Member

hehe yes!

@asterite
Copy link
Member

asterite commented Oct 4, 2022

image

@straight-shoota
Copy link
Member

straight-shoota commented Oct 8, 2022

It's great to know how this can be made to work.
But changing the linker instructions to be more explicit for interpreted mode is just a workaround.

This is unnecessary for native codegen because the linker on MacOS handles resolving the basic lib argument to the versioned instance internally. We should probably implement the same functionality in our loader implementation. The goal is that programs should work the same way whether executed natively or in the interpreter.

We can implement the workaround as a quick fix, but we should eventually fix it in the loader. But I guess for that we'd probably need some more information on how it actually works inside the linker, which is hard to judge from just a single trial-and-error obtained workaround that achieves (probably) the same result.

@will
Copy link
Contributor Author

will commented Oct 12, 2022

@HertzDevil
Copy link
Contributor

The loader is indeed missing processing for the RPATH and RUNPATH properties of the interpreter binary itself, but that shouldn't affect this scenario. (An interpreter built on Termux will have a nonempty RUNPATH, by the way)

@asterite
Copy link
Member

@HertzDevil @straight-shoota Thank you! I just tried it and I still get the same error. Was this fixed in Windows, or did you try it on a Mac?

@HertzDevil
Copy link
Contributor

This was tested on an M2, non-macOS systems should not have the abort stub

@asterite
Copy link
Member

This is what I get:

image

How can I debug what's wrong in my machine? Maybe I have a path override somewhere that makes it not work?

@straight-shoota
Copy link
Member

straight-shoota commented Feb 14, 2023

This patch should print diagnostics for the load order:

--- i/src/compiler/crystal/loader.cr
+++ w/src/compiler/crystal/loader.cr
@@ -85,11 +85,14 @@ class Crystal::Loader

   def load_library?(libname : String) : Bool
     if ::Path::SEPARATORS.any? { |separator| libname.includes?(separator) }
+      STDERR.puts "Loading file #{libname}"
       return load_file?(::Path[libname].expand)
     end

+    STDERR.puts "Loading lib #{libname}"
     @search_paths.each do |directory|
       library_path = File.join(directory, Loader.library_filename(libname))
+      STDERR.puts "  Trying #{library_path}"
       return true if load_file?(library_path)
     end

diff --git i/src/compiler/crystal/loader/unix.cr w/src/compiler/crystal/loader/unix.cr
index fb26f5842..7793a0c26 100644
--- i/src/compiler/crystal/loader/unix.cr
+++ w/src/compiler/crystal/loader/unix.cr
@@ -81,6 +81,7 @@ class Crystal::Loader
   end

   def load_file(path : String | ::Path) : Nil
+    STDERR.puts "Loading file #{path}"
     load_file?(path) || raise LoadError.new_dl_error "cannot load #{path}"
   end

@HertzDevil
Copy link
Contributor

What do these things look like?

  • The full ldflags command used to link libcrypto
  • The Crystal::Loader's instance variables
  • The final argument being passed to LibC.dlopen inside the loader

@asterite
Copy link
Member

@straight-shoota Thanks! Here's the output I got:

Loading lib z
  Trying /opt/homebrew/Cellar/crystal/1.7.0/libexec/../../../../lib/libz.dylib
Loading lib ssl
  Trying /opt/homebrew/Cellar/crystal/1.7.0/libexec/../../../../lib/libssl.dylib
WARNING: /Users/aryborenszweig/Projects/crystal/.build/crystal is loading libcrypto in an unsafe way

Here's the value of @search_paths before it fails:

["/opt/homebrew/Cellar/crystal/1.7.0/libexec/../../../../lib",
 "/opt/homebrew/Cellar/openssl@1.1/1.1.1s/lib",
 "/opt/homebrew/Cellar/openssl@1.1/1.1.1s/lib",
 "/opt/homebrew/Cellar/libevent/2.1.12/lib",
 "/usr/lib",
 "/usr/local/lib"]

The full ldflags command used to link libcrypto

I'm not sure where is that in the code.

The final argument being passed to LibC.dlopen inside the loader

It's /opt/homebrew/Cellar/crystal/1.7.0/libexec/../../../../lib/libssl.dylib

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

Successfully merging a pull request may close this issue.

7 participants