<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aviram Hassan</title>
    <description>The latest articles on DEV Community by Aviram Hassan (@aviramha).</description>
    <link>https://dev.to/aviramha</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F861592%2F508b75f5-49c5-4d08-b270-67e09a279463.png</url>
      <title>DEV Community: Aviram Hassan</title>
      <link>https://dev.to/aviramha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aviramha"/>
    <language>en</language>
    <item>
      <title>Fun with macOS's SIP</title>
      <dc:creator>Aviram Hassan</dc:creator>
      <pubDate>Thu, 22 Aug 2024 13:49:10 +0000</pubDate>
      <link>https://dev.to/aviramha/fun-with-macoss-sip-2646</link>
      <guid>https://dev.to/aviramha/fun-with-macoss-sip-2646</guid>
      <description>&lt;p&gt;While developing mirrord, which heavily relies on injecting itself into other people's binaries, we ran into some challenges posed by macOS’s SIP (System Integrity Protection). This post details how we ultimately overcame these challenges, and we hope it can be of help to other people hoping to learn about SIP, as we've learned the hard way that there's very little written about this subject on the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is mirrord?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://mirrord.dev" rel="noopener noreferrer"&gt;mirrord&lt;/a&gt; lets you run a local process in the context of a cloud service, which means we can test our code on our staging cluster without actually deploying it there. This leads to shorter feedback loops (you don’t have to wait on long CI processes to test your code in staging conditions) and a more stable staging environment (since untested services aren’t being deployed there). There is a detailed overview of mirrord in &lt;a href="https://metalbear.co/blog/mirrord-3.0-is-out/" rel="noopener noreferrer"&gt;this&lt;/a&gt; blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SIP and why does mirrord care about it?
&lt;/h2&gt;

&lt;p&gt;Apple introduced &lt;a href="https://en.wikipedia.org/wiki/System_Integrity_Protection" rel="noopener noreferrer"&gt;SIP&lt;/a&gt; in 2015 to prevent tampering with system binaries because those usually have high permissions and entitlements.&lt;br&gt;
mirrord works by injecting its library (.dylib or .so) into the local process, the one you want to “mirror” into the cloud. In order to inject itself into binaries, it uses &lt;code&gt;LD_PRELOAD&lt;/code&gt; on Linux and &lt;code&gt;DYLD_INSERT_LIBRARIES&lt;/code&gt; on macOS. &lt;br&gt;
One of the features of SIP is that it disallows the use of &lt;code&gt;DYLD_INSERT_LIBRARIES&lt;/code&gt; with protected binaries. Bummer - we can’t use mirrord to locally run e.g. &lt;code&gt;bash&lt;/code&gt; or &lt;code&gt;ls&lt;/code&gt; in a cloud context. we can’t run &lt;code&gt;mirrord exec bash&lt;/code&gt; or &lt;code&gt;mirrord exec ls&lt;/code&gt;. We’ll have to find a way to get around SIP!&lt;/p&gt;
&lt;h2&gt;
  
  
  Detecting if a binary is SIP-protected
&lt;/h2&gt;

&lt;p&gt;In order to start bypassing SIP, we needed to find a way to check if a binary is even SIP protected to begin with. We initially used pretty coarse heuristic, assuming that a binary is SIP-protected if it’s in one of these locations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;/System&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/bin&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/usr&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/sbin&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/var&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, we found that the “stat” function can return a flag called &lt;code&gt;RESTRICTED&lt;/code&gt; which we read could be related, and decided to use that instead&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. The code is quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;       &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;complete_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="nf"&gt;.st_flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;SF_RESTRICTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;SipStatus&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;SomeSIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;complete_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Shebang!
&lt;/h2&gt;

&lt;p&gt;Detection was simple enough when mirrord was run directly on a SIP-protected binary. However, we soon ran into a less trivial (but common) scenario - an interpreter script that starts with a shebang pointing to a SIP-protected binary, for example &lt;code&gt;yarn&lt;/code&gt;/&lt;code&gt;npm&lt;/code&gt;/&lt;code&gt;pyenv&lt;/code&gt;. If we look at &lt;code&gt;npm&lt;/code&gt; for instance, it points to a file which starts with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env node&lt;/span&gt;
require&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../lib/cli.js'&lt;/span&gt;&lt;span class="o"&gt;)(&lt;/span&gt;process&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example it will execute env, which will execute node with the next line.&lt;br&gt;
The problem? &lt;code&gt;/usr/bin/env&lt;/code&gt; is SIP protected, meaning it will strip our &lt;code&gt;DYLD_INSERT_LIBRARIES&lt;/code&gt; then run node without mirrord. Thanks for nothing SIP!&lt;br&gt;
So we also needed to check whether the file is a “shebang” file (i.e starts with #!), what file the shebang points to, and whether that file is a SIP-protected binary.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bypassing SIP
&lt;/h2&gt;

&lt;p&gt;Now that we found a way to detect whether we’re being run on a SIP-protected binary, we need to figure out how to bypass SIP and let mirrord load into the binary with &lt;code&gt;DYLD_INSERT_LIBRARIES&lt;/code&gt;. When googling around, we found people saying you can bypass SIP by copying the binary to another directory and re-signing it. We found that to be partially true.&lt;br&gt;
Why partially? Because if you tried to do it on Apple Silicon (arm), it wouldn’t work. This is because beginning with M1, macOS ships with arm64e binaries. The &lt;code&gt;e&lt;/code&gt; indicates an arm64 extension that adds pointer authentication. It’s another security measurement added by Apple (kudos to Apple for having great security, too bad it affects us).&lt;br&gt;
We won’t go into details about what pointer authentication does, but you can read more about it &lt;a href="https://googleprojectzero.blogspot.com/2019/02/examining-pointer-authentication-on.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
So why was this a problem? First, mirrord is written in Rust, which doesn’t support compiling arm64e binaries. The other problem is that only Apple-signed binaries can run with arm64e architecture.&lt;br&gt;
This is what happens if we try the “old” trick:&lt;br&gt;
➜  /tmp cp /usr/bin/env /tmp/env&lt;br&gt;
➜  /tmp codesign -f -s - /tmp/env&lt;br&gt;
/tmp/env: replacing existing signature&lt;br&gt;
➜  /tmp /tmp/env&lt;br&gt;
[1] 20114 killed    /tmp/env&lt;/p&gt;

&lt;p&gt;Killed! And it was so young. :(&lt;br&gt;
Recording using Console (macOS’s built in log viewer) while running the binary reveals the reason:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgn27di8zx366x0ctx6el.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgn27di8zx366x0ctx6el.png" alt="screenshot from console saying exec_mach_imgact: not running binary env built against preview arm64e ABI" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From Apple’s point of view, arm64e is preview only, i.e the ABI can change and they don’t want a third party building on top of it, as it’s not guaranteed to work. You can enable running third party executables with arm64e ABI only if you boot into recovery mode and change the settings, which is not something we want to ask our users to do. &lt;/p&gt;
&lt;h2&gt;
  
  
  Handling arm64e
&lt;/h2&gt;

&lt;p&gt;Initially we tried to convert the arm64e ABI into arm64 on the fly. Yes, people familiar with how this ABI works probably think we’re insane, but we were optimistic.. and it actually worked! for example, if you take &lt;code&gt;/usr/bin/env&lt;/code&gt; and just change the file headers to say it’s arm64, you’d be able to re-sign it and run it normally! We actually do it for our binaries to be able to load to arm64e binaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# from our release.yaml https://github.com/metalbear-co/mirrord/blob/main/.github/workflows/release.yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build mirrord-layer macOS arm/arm64e&lt;/span&gt;
      &lt;span class="c1"&gt;# Editing the arm64 binary, since arm64e can be loaded into both arm64 &amp;amp; arm64e&lt;/span&gt;
      &lt;span class="c1"&gt;# &amp;gt;&amp;gt; target/debug/libmirrord_layer.dylib: Mach-O 64-bit dynamically linked shared library arm64&lt;/span&gt;
      &lt;span class="c1"&gt;# &amp;gt;&amp;gt; magic bits: 0000000 facf feed 000c 0100 0000 0000 0006 0000&lt;/span&gt;
      &lt;span class="c1"&gt;# &amp;gt;&amp;gt; After editing using dd -&lt;/span&gt;
      &lt;span class="c1"&gt;# &amp;gt;&amp;gt; magic bits: 0000000 facf feed 000c 0100 0002 0000 0006 0000&lt;/span&gt;
      &lt;span class="c1"&gt;# &amp;gt;&amp;gt; target/debug/libmirrord_layer.dylib: Mach-O 64-bit dynamically linked shared library arm64e&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;cargo +nightly build --release -p mirrord-layer --target=aarch64-apple-darwin&lt;/span&gt;
        &lt;span class="s"&gt;cp target/aarch64-apple-darwin/release/libmirrord_layer.dylib target/aarch64-apple-darwin/release/libmirrord_layer_arm64e.dylib&lt;/span&gt;
        &lt;span class="s"&gt;printf '\x02' | dd of=target/aarch64-apple-darwin/release/libmirrord_layer_arm64e.dylib bs=1 seek=8 count=1 conv=notrunc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It didn’t work for all binaries though (&lt;code&gt;ls&lt;/code&gt; for example) and when we started digging we found out that there are a lot of new features being used in arm64e, for example specific relocations that contain pointer authentication stuff. We decided to give up on ABI conversion for the time being.&lt;br&gt;
Luckily, Apple ships fat binaries on both architecture machines. Fat binaries are Apple’s name for Mach-O files containing two different binaries, each built for a different architecture, so the runtime can decide which one it will use. By default, it will choose arm64e, but we can do something nice with the x64 binary.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```file /bin/ls&lt;br&gt;
/bin/ls: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64e:Mach-O 64-bit executable arm64e]&lt;br&gt;
/bin/ls (for architecture x86_64):    Mach-O 64-bit executable x86_64&lt;br&gt;
/bin/ls (for architecture arm64e):    Mach-O 64-bit executable arm64e&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

The idea is that we take the binary we want to load ourself into, extract only the x64 binary (on arm), re-sign it, and run it. The only downside here is that we require Rosetta[^2] to be installed on the system and there’s a performance impact - but usually system binaries are used for simple operations like `env` or `bash` (see the shebang case).

## Putting it all together

1. Detect SIP (Shebang/Restricted)
2. Patch

    a. Extract x64 binary into a new file

    b. chmod +x it

    c. Sign it

3. Execute



```rs
   /// Read the contents (or just the x86_64 section in case of a fat file) from the SIP binary at
   /// `path`, write it into `output`, give it the same permissions, and sign the new binary.
   fn patch_binary&amp;lt;P: AsRef&amp;lt;Path&amp;gt;, K: AsRef&amp;lt;Path&amp;gt;&amp;gt;(path: P, output: K) -&amp;gt; Result&amp;lt;()&amp;gt; {
       let data = std::fs::read(path.as_ref())?;
       let binary_info = BinaryInfo::from_object_bytes(&amp;amp;data)?;


       let x64_binary = &amp;amp;data[binary_info.offset..binary_info.offset + binary_info.size];
       std::fs::write(output.as_ref(), x64_binary)?;
       // Give the new file the same permissions as the old file.
       std::fs::set_permissions(
           output.as_ref(),
           std::fs::metadata(path.as_ref())?.permissions(),
       )?;
       codesign::sign(output)
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Integration into mirrord took two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When using mirrord directly on a SIP-protected binary, do the patch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When using mirrord on a process, and that process executes a SIP-protected binary, replace it on the fly. This was done by having mirrord hook &lt;code&gt;execve&lt;/code&gt; in the process&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;execve&lt;/code&gt; hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;
&lt;span class="cd"&gt;/// Hook for `libc::execve`.&lt;/span&gt;
&lt;span class="cd"&gt;///&lt;/span&gt;
&lt;span class="cd"&gt;/// Patch file if it is SIPed, use new path if patched.&lt;/span&gt;
&lt;span class="cd"&gt;/// If any args in argv are paths to mirrord's temp directory, strip the temp dir part.&lt;/span&gt;
&lt;span class="cd"&gt;/// So if argv[1] is "/var/folders/1337/mirrord-bin/opt/homebrew/bin/npx"&lt;/span&gt;
&lt;span class="cd"&gt;/// Switch it to "/opt/homebrew/bin/npx"&lt;/span&gt;
&lt;span class="cd"&gt;/// then call normal execve with the possibly updated path and argv and the original envp.&lt;/span&gt;
&lt;span class="nd"&gt;#[hook_guard_fn]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;execve_detour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;c_char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;c_char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;envp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;c_char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;c_int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// Do unsafe part of path conversion here.&lt;/span&gt;
   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rawish_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.is_null&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.then&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nn"&gt;CStr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;patched_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CString&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;final_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;patch_if_sip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawish_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;CString&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="n"&gt;patched_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c_string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
               &lt;span class="nf"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patched_path&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
           &lt;span class="p"&gt;}&lt;/span&gt;
           &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Null&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
       &lt;span class="p"&gt;})&lt;/span&gt;
       &lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Continue even if there were errors - just run without patching.&lt;/span&gt;


   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;argv_arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Nul&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_unchecked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


   &lt;span class="c1"&gt;// If we intercept args, we create a new array.&lt;/span&gt;
   &lt;span class="c1"&gt;// execve takes a null terminated array of char pointers.&lt;/span&gt;
   &lt;span class="c1"&gt;// ptr_vec will own the vector that will be passed to execve as an array.&lt;/span&gt;
   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;ptr_vec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;c_char&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;final_argv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;intercept_tmp_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv_arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;new_vec&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="n"&gt;ptr_vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_vec&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Make sure vector still lives when we pass the pointer to execve.&lt;/span&gt;
           &lt;span class="n"&gt;ptr_vec&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       &lt;span class="p"&gt;})&lt;/span&gt;
       &lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Call execve's default implementation&lt;/span&gt;
   &lt;span class="nf"&gt;FN_EXECVE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_argv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;envp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus content: Why is this possible?
&lt;/h2&gt;

&lt;p&gt;You might be asking yourself, “If this is a security feature by Apple, why is it possible to just bypass it that way?” The answer is that we do not bypass the security feature, just the problem it created for us. Apple operating systems have the concept of &lt;a href="https://developer.apple.com/documentation/bundleresources/entitlements" rel="noopener noreferrer"&gt;“entitlements”&lt;/a&gt;, which are definitions of which special operations an executable is allowed to perform, and which special resources it should have access to. Before released applications can have entitlements, they need to go through some approval process with Apple. Shared libraries get the entitlements of the host executable, so if we could load any library to any process, a non-Apple-approved library could enjoy entitlements it shouldn’t have by loading into an entitled process. That would be a pretty straight forward privilege escalation of that library’s code. SIP and the &lt;a href="https://developer.apple.com/documentation/security/hardened_runtime" rel="noopener noreferrer"&gt;hardened runtime&lt;/a&gt; prevent that from happening.&lt;/p&gt;

&lt;p&gt;When we, in our bypassing mechanism, copy an executable and resign it, it loses its entitlements. So it is still guaranteed that our dynamic library could not run with any ungranted entitlements. The integrity of granted entitlements is preserved.&lt;br&gt;
The loss of entitlements is not a problem for mirrord, because we do not expect to ever execute with mirrord any application that requires Apple entitlements. So we give up the mirrored application’s entitlements (which do not exist or are not needed), in order to be able to load our library into that application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Afterword
&lt;/h2&gt;

&lt;p&gt;You’re welcome to check out the full implementation in our &lt;a href="https://github.com/metalbear-co/mirrord/tree/main/mirrord/sip" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;, Join our &lt;a href="https://discord.gg/metalbear" rel="noopener noreferrer"&gt;Backend Engineers Discord&lt;/a&gt; community, subscribe to our newsletter and of course use mirrord!&lt;/p&gt;

&lt;p&gt;Originally posted in &lt;a href="https://metalbear.co/blog/fun-with-macoss-sip/" rel="noopener noreferrer"&gt;MetalBear's Blog&lt;/a&gt; in January 24, 2023.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;But in fact there might be other locations that are SIP protected, and if someone has a good and reliable way to detect if a binary is SIP, we’d love to hear it. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>programming</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Hooking Go from Rust - Hitchhiker’s Guide to the Go-laxy</title>
      <dc:creator>Aviram Hassan</dc:creator>
      <pubDate>Wed, 12 Oct 2022 11:53:59 +0000</pubDate>
      <link>https://dev.to/aviramha/hooking-go-from-rust-hitchhikers-guide-to-the-go-laxy-1bj5</link>
      <guid>https://dev.to/aviramha/hooking-go-from-rust-hitchhikers-guide-to-the-go-laxy-1bj5</guid>
      <description>&lt;p&gt;Most mainstream programming languages strive to fit into a few common standards, to increase interoperability and decrease adoption friction. Golang isn’t one of those (there &lt;a href="https://spectralops.io/blog/rust-vs-go-why-not-use-both/"&gt;are&lt;/a&gt; &lt;a href="https://words.filippo.io/rustgo/"&gt;several&lt;/a&gt; &lt;a href="https://fasterthanli.me/articles/lies-we-tell-ourselves-to-keep-using-golang"&gt;articles&lt;/a&gt; on the subject). In this blog post we’ll demonstrate how to overcome Go’s isolationist design and integrate with it from another language (in our case Rust).&lt;/p&gt;

&lt;p&gt;Why do we need to interop with Go? &lt;a href="https://metalbear.co/blog/mirrord-internals-hooking-libc-functions-in-rust-and-fixing-bugs/"&gt;mirrord works by hooking system calls to the operating systems&lt;/a&gt; and applying logic that decides whether to execute locally or remotely. To do that, mirrord side-loads (using &lt;code&gt;LD_PRELOAD&lt;/code&gt;) into the process, then hooks relevant functions.&lt;br&gt;
To cover most common scenarios, mirrord hooks libc functions and this works for most common languages (Python, Go on macOS, Rust, Node to name a few) as they all rely on libc.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mostly Harmless
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://lwn.net/Articles/771441/"&gt;Golang doesn’t use libc on Linux&lt;/a&gt;, and instead calls syscalls directly. This is mostly harmless for the common developer - they don’t care about the assembly, syscalls, linkage, etc - they just want their binary to work. Therefore, being self-contained provides a very good user experience, as Go applications aren’t dependent on the local machine’s libc.&lt;/p&gt;

&lt;p&gt;It’s pretty harmful for us, though. Since we explicitly override libc functions, our software simply doesn’t function when run with Go apps (or any other process that doesn’t call libc). Therefore, we must hook Golang functions!&lt;/p&gt;
&lt;h2&gt;
  
  
  Almost, but not quite, entirely unlike tea
&lt;/h2&gt;

&lt;p&gt;Luckily for us, Go applications are not entirely unlike other software. Golang &lt;strong&gt;has&lt;/strong&gt; to work with the operating system, so it has to use syscalls. Since libc doesn’t add much logic on top of the syscalls it wraps, we can still use all our existing code - we just have to override a different function with it.&lt;/p&gt;

&lt;p&gt;How do we hook Golang functions? Same way we do libc functions -  with &lt;a href="http://frida.re/"&gt;Frida&lt;/a&gt;. The problem is that writing Rust code that can work from a Go routine call state isn’t trivial. Go has its own ABI&lt;sup id="fnref1"&gt;1&lt;/sup&gt;, which doesn’t conform to any common ABI. This nonconformance is relatively common, though. For example, Rust also has an unstable internal ABI. If we could recompile the Go binary before side-loading into it, we could use cgo to have standard C ABI accessible, but in our use case we can’t. This means we have to implement a &lt;a href="https://en.wikipedia.org/wiki/Trampoline_(computing)"&gt;trampoline&lt;/a&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1Z7FJziK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9jur72seg4hg61byg8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1Z7FJziK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9jur72seg4hg61byg8q.png" alt="rust, go, asm trampoline" width="880" height="825"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The trampoline will be written in Assembly and its purpose is to translate a Go function call into a Rust function call, then return the result as the caller of the original Go function expected it to return.&lt;/p&gt;

&lt;p&gt;Looking at the backtrace of our Go &lt;a href="https://github.com/metalbear-co/mirrord/blob/main/tests/go-e2e/main.go"&gt;binary&lt;/a&gt; and dependencies of the &lt;code&gt;net/http&lt;/code&gt; package, it was obvious that it involved the use of the &lt;code&gt;syscall&lt;/code&gt; package. By reverse engineering the Go binary using &lt;a href="https://github.com/NationalSecurityAgency/ghidra"&gt;Ghidra&lt;/a&gt;, we mapped out the relevant flows (socket, listen, accept, etc.) to three different functions that we need to hook:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;syscall.Syscall6.abi0&lt;/code&gt; - syscalls with 6 parameters letting the runtime know we switched to a blocking operation so it can schedule on another thread/goroutine.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;syscall.Syscall.abi0&lt;/code&gt; - same as &lt;code&gt;syscall.Syscall6.abi0&lt;/code&gt; but with three parameters.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;syscall.RawSyscall.abi0&lt;/code&gt; - same as the above but without notifying the runtime.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Don’t Panic
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Big Jump
&lt;/h3&gt;

&lt;p&gt;Let’s start with a very basic trampoline, hooking &lt;code&gt;syscall.RawSyscall.abi0&lt;/code&gt; (a routine that calls a syscall with 3 parameters, also used by &lt;code&gt;socket&lt;/code&gt; in the syscall package). Below is the disassembly of this function:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KOu3rBCF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lk08yrmcrdpkjfigb9uh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KOu3rBCF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lk08yrmcrdpkjfigb9uh.png" alt="disassembly of syscall.RawSyscall.abi0 using Ghidra" width="880" height="781"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will implement this trampoline by moving arguments from the stack to registers as Rust expects in a C ABI, then return the result on the stack as Go expects.&lt;/p&gt;
&lt;h3&gt;
  
  
  From stack to registers
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov rsi, QWORD PTR [rsp+0x10]
mov rdx, QWORD PTR [rsp+0x18]
mov rcx, QWORD PTR [rsp+0x20]
mov rdi, QWORD PTR [rsp+0x8]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Golang has its own ABI(as mentioned before), precisely &lt;code&gt;ABI0&lt;/code&gt; and &lt;code&gt;ABIInternal&lt;/code&gt;. Go keeps &lt;a href="https://go.googlesource.com/proposal/+/master/design/27539-internal-abi.md"&gt;backward compatibility&lt;/a&gt; with a stack based calling convention along with the recently introduced register based calling convention. Turns out &lt;code&gt;ABI0&lt;/code&gt; functions follow a stack based convention, which is why we move values from the stack rather than registers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Calling the handler
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;call c_abi_syscall_handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Following the stack based convention from Go, we move the arguments to registers. But what registers exactly and why? Since we’re hooking a function that directly makes the syscall, we would require a handler to manage the syscalls for us. Our handler will be called using the C ABI calling convention, it will match on syscalls and redirect them based on their type to their specific detours and return the result in the specific register conforming to the C ABI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[no_mangle]&lt;/span&gt;
&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;c_abi_syscall_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;param3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;        
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;        
        &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SYS_socket&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param1&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param3&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            
            &lt;span class="n"&gt;sock&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Putting it back on the stack for Go
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://go.googlesource.com/go/+/c0d6d33/src/syscall/asm_linux_amd64.s"&gt;asm_linux_amd64.s&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// func Syscall(trap int64, a1, a2, a3 uintptr) (r1, r2, err uintptr);
// Trap # in AX, args in DI SI DX R10 R8 R9, return in AX DX
// Note that this differs from "standard" ABI convention, which
// would pass 4th arg in CX, not R10.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned above and as we saw in the disassembly, we will move the result returned by the handler back to the stack like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
mov  QWORD PTR [rsp+0x28],rax
mov  QWORD PTR [rsp+0x30],rdx
mov  QWORD PTR [rsp+0x38],0x0
ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summing it up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[cfg(target_os&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"linux"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="nd"&gt;#[cfg(target_arch&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"x86_64"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="nd"&gt;#[naked]&lt;/span&gt;
&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;go_raw_syscall_detour&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;asm!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"mov rsi, QWORD PTR [rsp+0x10]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"mov rdx, QWORD PTR [rsp+0x18]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"mov rcx, QWORD PTR [rsp+0x20]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"mov rdi, QWORD PTR [rsp+0x8]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"call c_abi_syscall_handler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"mov  QWORD PTR [rsp+0x28],rax"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"mov  QWORD PTR [rsp+0x30],rdx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"mov  QWORD PTR [rsp+0x38],0x0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"ret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;noreturn&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the usage of the &lt;a href="https://rust-lang.github.io/rfcs/2972-constrained-naked.html"&gt;Naked function feature&lt;/a&gt;. Naked functions give us full control over the generated assembly(as needed in our use case) since Rust doesn’t generate an epilogue/prologue for them.&lt;/p&gt;

&lt;p&gt;Let’s do a sample run and see if everything works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BPaN4-EL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6pzhxe6ihjxv7o26w2jr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BPaN4-EL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6pzhxe6ihjxv7o26w2jr.png" alt="running the gin server with the rawsyscall hook" width="880" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! It works just as we expected. However, the actual detours in mirrord contain logs and do a lot of book-keeping. Let’s start by adding a simple debug statement and see where things go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[no_mangle]&lt;/span&gt;
&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;c_abi_syscall_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;param3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;    
    &lt;span class="nd"&gt;debug!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c_abi_sycall_handler received syscall: {syscall:?}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;        
        &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SYS_socket&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param1&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param3&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            
            &lt;span class="n"&gt;sock&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And action!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mehula@mehul-machine:~/golang-e2e/server&lt;span class="nv"&gt;$ LD_PRELOAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;../target/debug/libmirrord.so ./server
2022-08-15T17:15:36.497241Z DEBUG mirrord: LD_PRELOAD SET
2022-08-15T17:15:36.498403Z DEBUG mirrord: &lt;span class="s2"&gt;"syscall.RawSyscall.abi0"&lt;/span&gt; hooked
Server listening on port 8080
2022-08-15T17:15:36.505606Z DEBUG mirrord: c_abi_sycall_handler received syscall: 41
2022-08-15T17:15:36.505689Z DEBUG mirrord: c_abi_sycall_handler received syscall: 41
2022-08-15T17:15:36.505738Z DEBUG mirrord: c_abi_sycall_handler received syscall: 41
unexpected fault address 0x0
fatal error: fault
&lt;span class="o"&gt;[&lt;/span&gt;signal SIGSEGV: segmentation violation &lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x80 &lt;span class="nv"&gt;addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x0 &lt;span class="nv"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x7fa45a87f6b2]

goroutine 1 &lt;span class="o"&gt;[&lt;/span&gt;running]:
runtime.throw&lt;span class="o"&gt;({&lt;/span&gt;0x7e0b21?, 0x46?&lt;span class="o"&gt;})&lt;/span&gt;
        /usr/local/go/src/runtime/panic.go:992 +0x71 &lt;span class="nv"&gt;fp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0xc0002372a8 &lt;span class="nv"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0xc000237278 &lt;span class="nv"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x4354b1
runtime: unexpected &lt;span class="k"&gt;return &lt;/span&gt;pc &lt;span class="k"&gt;for &lt;/span&gt;runtime.sigpanic called from 0x7fa45a87f6b2
stack: &lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;sp:0xc0002372a8, fp:0xc0002372f8&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;0xc000218000,0xc000238000&lt;span class="o"&gt;)&lt;/span&gt;
0x000000c0002371a8:  0x000000000045551b &amp;lt;runtime.write+0x000000000000003b&amp;gt;  0x0000000000000002 
0x000000c0002371b8:  0x000000c0002371f0  0x0000000000436bae &amp;lt;runtime.recordForPanic+0x000000000000004e&amp;gt; 
0x000000c0002371c8:  0x000000000045551b &amp;lt;runtime.write+0x000000000000003b&amp;gt;  0x0000000000000002 
0x000000c0002371d8:  0x00000000008833ec  0x0000000000000001 
0x000000c0002371e8:  0x0000000000000001  0x000000c000237228 
0x000000c0002371f8:  0x0000000000436eb2 &amp;lt;runtime.gwrite+0x00000000000000f2&amp;gt;  0x00000000008833ec 
0x000000c000237208:  0x0000000000000001  0x0000000000000001 
0x000000c000237218:  0x000000c000237295  0x0000000000000003 
0x000000c000237228:  0x000000c000237278  0x000000000046274e &amp;lt;runtime.systemstack+0x000000000000002e&amp;gt; 
0x000000c000237238:  0x00000000004356f0 &amp;lt;runtime.fatalthrow+0x0000000000000050&amp;gt;  0x000000c000237248 
0x000000c000237248:  0x0000000000435720 &amp;lt;runtime.fatalthrow.func1+0x0000000000000000&amp;gt;  0x000000c0000021a0 
0x000000c000237258:  0x00000000004354b1 &amp;lt;runtime.throw+0x0000000000000071&amp;gt;  0x000000c000237278 
0x000000c000237268:  0x000000c000237298  0x00000000004354b1 &amp;lt;runtime.throw+0x0000000000000071&amp;gt; 
0x000000c000237278:  0x000000c000237280  0x00000000004354e0 &amp;lt;runtime.throw.func1+0x0000000000000000&amp;gt; 
0x000000c000237288:  0x00000000007e0b21  0x0000000000000005 
0x000000c000237298:  0x000000c0002372e8  0x000000000044a8c5 &amp;lt;runtime.sigpanic+0x0000000000000305&amp;gt; 
0x000000c0002372a8: &amp;lt;0x00000000007e0b21  0x0000000000000046 
0x000000c0002372b8:  0x00007fa45a0f27c0  0x00007fa45a89e8e9 
0x000000c0002372c8:  0x0000000000000000  0x0000000000000000 
0x000000c0002372d8:  0x00007fa45a0f27c0  0x0000000000000000 
0x000000c0002372e8:  0x000000c000237ab0 &lt;span class="o"&gt;!&lt;/span&gt;0x00007fa45a87f6b2 
0x000000c0002372f8: &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;0x000000c000237320  0x000000c0002373e8 
0x000000c000237308:  0x00007fa45a0f27c0  0x0000000000000000 
0x000000c000237318:  0x00007fa45a0f27c0  0x00007fa45a0f27c0 
0x000000c000237328:  0x00007fa45a0f27c0  0x0000000000000000 
0x000000c000237338:  0x0000000000000000  0x0000000000000000 
0x000000c000237348:  0x00007fa45a895d84  0x0000000000000000 
0x000000c000237358:  0x000000000237c328  0x00007fa45a0f2840 
0x000000c000237368:  0x0000000000000000  0x00007fa45b698f50 
0x000000c000237378:  0x0000000000000000  0xffffffffffffffff 
0x000000c000237388:  0x00007fa45a0f2840  0x0000000000000000 
0x000000c000237398:  0x00007fa45a0f2840  0xffffffffffffffff 
0x000000c0002373a8:  0x0000000000000000  0x00007fa45a0f27c0 
0x000000c0002373b8:  0x00007fa45a0f27c0  0x00007fa45a0f27c0 
0x000000c0002373c8:  0x00007fa45a0f27c0  0x00007fa45a87e8e7 
0x000000c0002373d8:  0x0000000000000000  0x00007fa45a895d44 
0x000000c0002373e8:  0x000000c000237420  0x000000000237c340 
runtime.sigpanic&lt;span class="o"&gt;()&lt;/span&gt;
        /usr/local/go/src/runtime/signal_unix.go:825 +0x305 &lt;span class="nv"&gt;fp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0xc0002372f8 &lt;span class="nv"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0xc0002372a8 &lt;span class="nv"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x44a8c5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why did this goroutine panic with our hook?
&lt;/h3&gt;

&lt;p&gt;Go’s runtime scheduler follows a very peculiar yet smart way of scheduling goroutines. The scheduler mainly works on four important objects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;G - The goroutine&lt;/li&gt;
&lt;li&gt;N - Number of goroutines&lt;/li&gt;
&lt;li&gt;M - OS thread (N is mapped to M)&lt;/li&gt;
&lt;li&gt;P - Represents the notion of a processor i.e. resource provider for M when its running a goroutine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As described in the &lt;a href="https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit"&gt;design document&lt;/a&gt; for the Go runtime scheduler,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“When a new G is created or an existing G becomes runnable, it is pushed onto a list of runnable goroutines of current P. When P finishes executing G, it first tries to pop a G from own list of runnable goroutines; if the list is empty, P chooses a random victim (another P) and tries to steal a half of runnable goroutines from it.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In summary, every G runs on an M assigned to a P.&lt;/p&gt;

&lt;p&gt;Now that we know a bit about how Go schedules goroutines, by looking at &lt;a href="https://cs.opensource.google/go/go/+/master:src/runtime/stack.go"&gt;this&lt;/a&gt; source file we can see Golang doesn’t work with the “system stack” (on Linux in most cases, the pthread stack) but with its own goroutine stack implementation with a minimum size of 2048 bytes.&lt;/p&gt;

&lt;p&gt;Goroutine stack is dynamic, i.e. it is constantly expanding/shrinking depending on the current needs. This means any common code that runs in system stack assumes it can grow as it wishes (until it exceeds max stack size) while actually, it can’t unless using Go APIs for expanding. Our Rust code isn’t aware of it, so it uses parts of the stack that aren't actually usable and causes stack overflow.&lt;/p&gt;

&lt;p&gt;We lack some steps. One might consider using &lt;code&gt;runtime.morestack&lt;/code&gt;, but that's probably not ideal for us because that involves managing the stack manually per our needs. Luckily, we aren’t the first ones to do FFI&lt;sup id="fnref3"&gt;3&lt;/sup&gt; in Go, so we looked into what cgo does when calling foreign functions:&lt;/p&gt;

&lt;p&gt;Referring &lt;a href="https://go.dev/src/runtime/cgocall.go"&gt;runtime/cgocall.go&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Cgo call and callback support.1
//
// To call into the C function f from Go, the cgo-generated code calls
// runtime.cgocall(_cgo_Cfunc_f, frame), where _cgo_Cfunc_f is a
// gcc-compiled function written by cgo.
//
// runtime.cgocall (below) calls entersyscall so as not to block
// other goroutines or the garbage collector, and then calls
// runtime.asmcgocall(_cgo_Cfunc_f, frame).
//
// runtime.asmcgocall (in asm_$GOARCH.s) switches to the m-&amp;gt;g0 stack
// (assumed to be an operating system-allocated stack, so safe to run
// gcc-compiled code on) and calls _cgo_Cfunc_f(frame).
//
// _cgo_Cfunc_f invokes the actual C function f with arguments
// taken from the frame structure, records the results in the frame,
// and returns to runtime.asmcgocall.
//
// After it regains control, runtime.asmcgocall switches back to the
// original g (m-&amp;gt;curg)'s stack and returns to runtime.cgocall.
//
// After it regains control, runtime.cgocall calls exitsyscall, which blocks
// until this m can run Go code without violating the $GOMAXPROCS limit,
// and then unlocks g from m.
//

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will skip the non-blocking part i.e. calling runtime.entersyscall/runtime.exitsyscall for letting the scheduler beware of the “blocking” call so that the scheduler can yield its time to another goroutine as seen in the case of &lt;code&gt;Syscall.Syscall6.abi0&lt;/code&gt; and &lt;code&gt;Syscall.Syscall.abi0&lt;/code&gt;. Therefore, we just replace the stack from the goroutine to the system stack using the implementation of&lt;code&gt;runtime.asmcgocall.abi0&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov rbx, QWORD PTR [rsp+0x10]
mov r10, QWORD PTR [rsp+0x18]
mov rcx, QWORD PTR [rsp+0x20]
mov rax, QWORD PTR [rsp+0x8]
mov    rdx, rsp
mov    rdi, QWORD PTR fs:[0xfffffff8]
cmp    rdi, 0x0
je     2f
mov    r8, QWORD PTR [rdi+0x30]
mov    rsi, QWORD PTR [r8+0x50]
cmp    rdi, rsi
je     2f
mov    rsi, QWORD PTR [r8]
cmp    rdi, rsi
je     2f
call   go_systemstack_switch
mov    QWORD PTR fs:[0xfffffff8], rsi
mov    rsp, QWORD PTR [rsi+0x38]
sub    rsp, 0x40
and    rsp, 0xfffffffffffffff0
mov    QWORD PTR [rsp+0x30], rdi
mov    rdi, QWORD PTR [rdi+0x8]
sub    rdi, rdx
mov    QWORD PTR [rsp+0x28],rdi
mov    rsi, rbx
mov    rdx, r10
mov    rdi, rax
call   c_abi_syscall_handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saving the arguments in some untouched registers we call the handler on the system stack, and shuffle the registers/stack data to match Go’s expectations, mainly returning parameters to be in a specific place in the stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov    QWORD PTR [rsp+0x28], -0x1
mov    QWORD PTR [rsp+0x30], 0x0
neg    rax
mov    QWORD PTR [rsp+0x38], rax
xorps  xmm15, xmm15
mov    r14, QWORD PTR FS:[0xfffffff8]
ret
3:
mov    QWORD PTR [rsp+0x28], rax
mov    QWORD PTR [rsp+0x30], 0x0
mov    QWORD PTR [rsp+0x38], 0x0
xorps  xmm15, xmm15
mov    r14, QWORD PTR FS:[0xfffffff8]
ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After stitching together all the &lt;code&gt;ABI0&lt;/code&gt; syscall detours with mirrord, let’s look if things work as expected -&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cTcNlCQ_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvydzskk7h5ykjw3hswi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cTcNlCQ_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvydzskk7h5ykjw3hswi.png" alt="running the gin server with the rawsyscall hook" width="880" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Success! 🥂&lt;/p&gt;

&lt;p&gt;Complete implementation of all hooks is available &lt;a href="https://github.com/metalbear-co/mirrord/blob/main/mirrord-layer/src/go_hooks.rs"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We decided &lt;strong&gt;not&lt;/strong&gt; to handle the non-blocking changes that Go makes, primarily because it doesn’t really matter for our use-case (having “a bit of delay” isn’t critical to the value we try to provide with mirrord). We are planning to address it later on, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Long, and Thanks for All the Fish
&lt;/h2&gt;

&lt;p&gt;One of the ideas we had while working on this was to write a framework that will provide APIs to hook Go functions, i.e make trampolines from Rust using proc macros. It felt like too big of a project, and what we ended up doing suits our current needs, but if anyone is up for working on such a framework, we’d be happy to sponsor it! We’d love to hear your feedback and thoughts in our Backend Engineers community on &lt;a href="https://discord.com/invite/J5YSrStDKD"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to checkout &lt;a href="https://github.com/metalbear-co/mirrord"&gt;mirrord&lt;/a&gt;, send corrections/issues with the blog post on our &lt;a href="https://github.com/metalbear-co/metalbear.co"&gt;website’s repository&lt;/a&gt; or just reach us at &lt;a href="mailto:hi@metalbear.co"&gt;hi@metalbear.co&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Originally posted in &lt;a href="https://metalbear.co/blog/hooking-go-from-rust-hitchhikers-guide-to-the-go-laxy/"&gt;MetalBear's blog.&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Application binary interface - how binary programs interact, how do they use the stack, registers, etc. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;The cool Gopher was made using &lt;a href="https://gopherize.me/"&gt;https://gopherize.me/&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Foreign Function Interface - How to interact with external functions provided by other frameworks/dependencies. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>rust</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Carcinisation of mirrord (or: why we use Rust)</title>
      <dc:creator>Aviram Hassan</dc:creator>
      <pubDate>Tue, 16 Aug 2022 06:59:53 +0000</pubDate>
      <link>https://dev.to/aviramha/carcinisation-of-mirrord-or-why-we-use-rust-5258</link>
      <guid>https://dev.to/aviramha/carcinisation-of-mirrord-or-why-we-use-rust-5258</guid>
      <description>&lt;p&gt;&lt;strong&gt;Carcinisation&lt;/strong&gt; (or &lt;strong&gt;carcinization&lt;/strong&gt;) is an example of &lt;a href="https://en.wikipedia.org/wiki/Convergent_evolution"&gt;convergent evolution&lt;/a&gt; in which a &lt;a href="https://en.wikipedia.org/wiki/Crustacean"&gt;crustacean&lt;/a&gt; &lt;a href="https://en.wikipedia.org/wiki/Evolution"&gt;evolves&lt;/a&gt; into a &lt;a href="https://en.wikipedia.org/wiki/Crab"&gt;crab&lt;/a&gt;-like form from a non-crab-like form. (source: &lt;a href="https://en.wikipedia.org/wiki/Carcinisation"&gt;Wikipedia&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A classic example of carcinisation is MetalBear's mirrord project, where several different components converged on Rust as their main language. In this post, we'll detail their different evolutionary paths and explain why they ended up being written in Rust.&lt;/p&gt;

&lt;h1&gt;
  
  
  First of all, what is mirrord?
&lt;/h1&gt;

&lt;p&gt;mirrord is an open-source tool that lets developers run local processes in the context of their cloud environment. It is meant to provide the benefits of running your service in a cloud environment (e.g. staging) without going through the hassle of actually deploying it there, and without disrupting the environment by deploying untested code.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VlwNMnDn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ssr3y29rl3tm2fidk6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VlwNMnDn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ssr3y29rl3tm2fidk6v.png" alt="Graphical illustration of mirrord architecture" width="880" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;mirrord has four main components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent - Runs in the cloud and acts as proxy for the users.&lt;/li&gt;
&lt;li&gt;Layer - Shared library that runs inside the local service, hooking IO operations (sockets, filesystem) and proxying those to the agent.&lt;/li&gt;
&lt;li&gt;CLI - Wrapper to inject/load the Layer into local processes.&lt;/li&gt;
&lt;li&gt;VS Code Extension - Same as CLI, but for VSCode.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why Rust?
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Agent
&lt;/h2&gt;

&lt;p&gt;This component is shipped as a container image. The layer creates a job with this image, providing it with elevated permissions on the same node as the &lt;a href="https://mirrord.dev/docs/overview/architecture/#mirrord-agent"&gt;impersonated pod&lt;/a&gt;. The job then enters the impersonated pod's namespaces in order to be able to access its file system and network interfaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switching Namespaces
&lt;/h3&gt;

&lt;p&gt;Ideally, we would like to switch namespaces only for the necessary code flows, so we can have &lt;strong&gt;minimal&lt;/strong&gt; impact on the impersonated pod. Linux lets you control namespace based on threads, so you can have different functionalities running in different threads and on different namespaces. Controlling threads this way &lt;em&gt;can&lt;/em&gt; be hard in some frameworks/languages. For example, in Go, threads &lt;a href="https://www.weave.works/blog/linux-namespaces-golang-followup"&gt;are actually abstracted so you&lt;/a&gt; need to do some tinkering to ensure correctness. On the other hand, Rust doesn't really abstract anything, and offers very fine tuned control over threads and namespaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;One of mirrord's goals is to let multiple developers work on the same environment without impacting each other. To achieve that, our agent has have a very small footprint. Rust lets us have a &lt;em&gt;fixed-size&lt;/em&gt; memory layout, without many allocations and without the &lt;a href="https://discord.com/blog/why-discord-is-switching-from-go-to-rust"&gt;overhead of a garbage collector.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Thread Safety
&lt;/h3&gt;

&lt;p&gt;The Agent has many functionalities running at the same time, requiring the ability to move data between threads safely.&lt;/p&gt;

&lt;p&gt;Rust provides great primitives and safety around concurrency and task management. It doesn't let us send data types that are thread-bound unknowingly thanks to &lt;a href="https://doc.rust-lang.org/std/marker/trait.Send.html"&gt;Send&lt;/a&gt; and it warns us when we hold references to non-atomic data types across threads with &lt;a href="https://doc.rust-lang.org/std/marker/trait.Sync.html"&gt;Sync&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer
&lt;/h2&gt;

&lt;p&gt;This component is shipped as a dylib/so (shared library) and loaded into the local process that's being plugged into the cloud. Once loaded, it hooks many libc functions (and some other frameworks, such us libuv) to create a smart management layer that decides what operation happens locally and what is being relayed to run remotely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Low Level
&lt;/h3&gt;

&lt;p&gt;It was obvious to us that the layer had to be written in a low level language. Yes, we could create a bridge layer&lt;sup id="fnref2"&gt;2&lt;/sup&gt; (JS actually has one built in with Frida) but that would add complexity and security concerns. Whereas in other parts of the solution we run in a self-contained context (i.e. our own process), this time we're being loaded into a process that isn't aware of our existence and we can't introduce bugs into it. Rust + Frida let us hook low level functions such as &lt;code&gt;accept&lt;/code&gt;, &lt;code&gt;bind&lt;/code&gt;, &lt;code&gt;socket&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt;, etc in a relatively safe manner leveraging great abstractions such as &lt;code&gt;Arc&lt;/code&gt; to manage our internal data structures and sync primitives such as &lt;code&gt;mpsc&lt;/code&gt; to build communication between different parts of our code (in the layer, it's mainly between the "main loop thread" and hooks).&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;Like the Agent, the Layer also needs to have low overhead in order to provide great developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLI
&lt;/h2&gt;

&lt;p&gt;The CLI is responsible for injecting the layer into the target process. Right now, our main (and only) load mechanism is using &lt;code&gt;DYLD_INSERT_LIBRARIES&lt;/code&gt; (on macOS) and &lt;code&gt;LD_PRELOAD&lt;/code&gt; on Linux.&lt;/p&gt;

&lt;p&gt;Implementing this in another language could've been easy but we decided to go with Rust for several reasons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future-proof
&lt;/h3&gt;

&lt;p&gt;The load method is fairly simple, but if in the future we'd want to introduce a more sophisticated injection method like using "ptrace" or other methods. Rust would let us implement it and use those functions and layouts at more comfort than other languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Standalone
&lt;/h3&gt;

&lt;p&gt;Rust generates standalone binaries (apart from libc). On top of that, using Cargo's new &lt;code&gt;bindeps&lt;/code&gt; feature we could embed &lt;a href="https://mirrord.dev/docs/overview/architecture/#mirrord-layer"&gt;mirrord-&lt;/a&gt;&lt;a href="https://mirrord.dev/docs/overview/architecture/#mirrord-layer"&gt;layer&lt;/a&gt; into the CLI, creating a smooth and transparent experience instead of having to ship two files or downloading dependencies at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  VS Code Extension
&lt;/h2&gt;

&lt;p&gt;The extension can't really be written in Rust due to VS Code support for JS only. We did consider using WASM, but the bridging logic would "cost" too much to be worth any value Rust might provide (which, for the extension, would mostly be consistency with our other components)..&lt;/p&gt;

&lt;h2&gt;
  
  
  Company
&lt;/h2&gt;

&lt;p&gt;Bonus section! I had a feeling having our codebase be mainly in Rust would make hiring engineers a lot easier. As a Rust enthusiast, I would have loved to work somewhere where I'd get to work with Rust regularly, and I suspected that many others felt the same. I even posted a poll in&lt;a href="https://www.reddit.com/r/rust/comments/tjbr92/employeeemployer_market_state_for_rust/"&gt;/r/rust&lt;/a&gt; to see how others feel. The results were encouraging - around 40% of the people who voted thought the market for hiring Rust engineer is employer driven. I suspect that in any other ecosystem, the results would have been much more one sided in favor of the market being employee driven. When we finally started hiring, we posted in the "Who's Hiring" mega thread in /r/rust, and received applications from some great candidates, making building the team both fun and fast.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FIVBZm6c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j122rykeozf6o73k58vr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FIVBZm6c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j122rykeozf6o73k58vr.png" alt="Meme of Rustacean trying to get a Rust job but then stolen by a non-Rust company" width="500" height="672"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Afterword
&lt;/h2&gt;

&lt;p&gt;Rust can be used for many use cases, software and applications. We believe that every task requires different tools, but in our case our project's whole ecosystem fit right into Rust. You always need to do your research before choosing a language and stack, but generally speaking Rust is powerful and versatile and can be an amazing choice for a lot of different solutions.&lt;/p&gt;

&lt;p&gt;Do you have any questions/corrections? Our website is completely open-source, so feel free to submit it as an issue or PR to our &lt;a href="https://github.com/metalbear-co/metalbear.co/"&gt;repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Want to help mirrord? Have a look at our open issues in the &lt;a href="https://github.com/metalbear-co/mirrord/issues"&gt;GitHub issue tracker&lt;/a&gt; and feel free to contribute.&lt;/p&gt;

&lt;p&gt;Originally posted in &lt;a href="https://metalbear.co/blog/carcinisation-of-mirrord-or-why-we-use-rust/"&gt;MetalBear's blog.&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://mirrord.dev/docs/overview/introduction/"&gt;https://mirrord.dev/docs/overview/introduction/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;A software layer that connects the low level C run time with the JS runtime (translating the hooks into JS function calls) ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>rust</category>
      <category>programming</category>
      <category>cloud</category>
      <category>vscode</category>
    </item>
  </channel>
</rss>
