Calling a private Rust function from outside of its module
Tags: rust, compilers
Published: 2021-03-06 22:10:44

---
title: Calling a private Rust function from outside of its module
published: true
description: Private functions should be, well, private. Let's learn how to break that!
tags: rust, compilers
canonical_url: https://tim.mcnamara.nz/post/644942576528523264/calling-a-private-rust-function-from-outside
cover_image: https://images.unsplash.com/photo-1543933441-40fbd6b34481?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80
---

Private functions should be, well, private. Let's learn how to break that! Understanding what's happening will provide you insight into how programming languages are implemented.

This party trick is attributed to [LordMZTE](https://twitter.com/LordMZTE). When I was discussing raw pointer manipulation on a live stream a few days' ago, they sent me a Rust playground link to explore. What I discovered was code doing something that I didn’t think was possible: call a function that’s private from outside of a module.

When [the code from the example below is executed](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9f24b9073668d80cbfc00619560d4bb8), it prints an interesting message:

> Private function called!

Take a look at the source and see if you can figure out what’s happening:


    #![allow(dead_code)]
    
    pub mod foo {
        pub fn public() {
            println!("Public function called");
            private();   
        }
        fn private() {
            println!("Private function called!");
        }
    }
    
    pub fn main() {
        // foo::public();
        
        unsafe {
            std::mem::transmute::<usize fn> ()>((foo::public as usize) + 64)();
        }
    }


So, how does it work? And where does 64 come from?

## Video Recording

If you prefer videos - you can watch a [recording of the stream](https://www.youtube.com/watch?v=).

{% youtube uQsDxlSojpM %}

## Explanation

The magic happens here: 


    std::mem::transmute::<usize fn> ()>((foo::public as usize) + 64)();

This line asks Rust to treat the memory address of the `foo::public` function as an unsigned integer, add 64 to that value, then treat that higher address as a function and call it.
 
Let’s break it apart to figure out how it works.

Functions are represented in memory as an array of bytes just like data. Unlike data, the CPU interprets them as code. To call the `foo::private` function from outside of the `foo` module, we need to find out where it is laid out in memory. The Rust compiler happens to lay functions right next to each other. So to find the start of `foo::private`, we can start with the location of `foo::public`  and add its length. 

Now to reveal a sleight of hand in the original example. I removed a call to the `dbg!()` macro, which I had executed in a previous execution:


    // finding the length of foo::public()
    
    #![allow(dead_code)]
    
    pub mod foo {
        pub fn public() {
            println!("Public function called");
            private();   
        }
        pub fn private() { // temporarily mark foo::private as public
            println!("Private function called!");
        }
    }
    
    pub fn main() {
        dbg!((foo::private as usize) - (foo::public as usize));
    }

If we [run this new version in the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f67ce26852808a0c2be11c3475116ddd), we’ll receive the length printed to the console:


    [src/main.rs:16] (foo::private as usize) - (foo::public as usize) = 64

With the length handy, we now know what `fn foo::private` refers to. As an implementation detail, the `fn` keyword creates a function pointer. Function pointers are pointers that point to executable memory.

To treat a function pointer as an integer, the code uses `<fn> as usize`. To treat a `usize` as a memory address though, we need more powerful tools. The `std::mem::transmute()` function is that tool. It is probably the most unsafe constructs in the Rust language. It instructs Rust to re-interpret data types. In our case,  `std::mem::transmute::<usize fn> ()>`  asks Rust to interpret a `usize` as a function pointer that takes 0 arguments and returns the “unit type” (`()`). 

## Discussion

### Is being able to call private functions a security hole?

No, not really. The program needs to be able to call private functions. We only knew which memory address to “call” because we had access to the source code. This enabled us to mark `foo::private` as public and deduce the length of `foo::public`.

That said, it is possible to scan a program’s address space looking for executable chunks. That’s exactly what viruses do.  But that doesn’t mean that Rust programs can arbitrarily violate their own privacy guards.

### What does “private” mean in the context of a programming language?

In some sense, privacy doesn’t exist. There is no guard watching modules to make sure that they’re not trespassing other modules. Privacy, at least within Rust as it’s currently implemented, is a compile-time construct. We can’t look inside the executable binary to find sections marked as private.

That said, programming language designers have the ability to define whatever compile-time they want. For example, the operating system has the ability to mark specific memory pages as illegal (look up “guard pages” for extra info). Attempts to access these specially marked sections of memory indicate a runtime fault and the process will be terminated. It would be possible to add one of these pages between private and public members of a module, but that would incur a memory overhead.

### What’s in those 64 bytes?

If you’re interested in what is happening in those 64 bytes, then you can ask the playground to print out the assembly generated by rustc. Look for the `playground::foo::public` section:


    playground::foo::public:
            subq        $56, %rsp
            leaq        .L__unnamed_2(%rip), %rax
            leaq        .L__unnamed_3(%rip), %rcx
            xorl        %edx, %edx
            movl        %edx, %r8d
            leaq        8(%rsp), %rdi
            movq        %rax, %rsi
            movl        $1, %edx
            callq        core::fmt::Arguments::new_v1
            leaq        8(%rsp), %rdi
            callq        *std::io::stdio::_print@GOTPCREL(%rip)
            callq        playground::foo::private
            addq        $56, %rsp
            retq 

Here is `foo::public`  again. Can you align the source code its assembly? 


        pub fn public() {
            println!("Public function called");
            private();   
        }

Here are some hints for the curious. Many of the the opcodes are suffixed with `q` meaning quad, or `l` for long. Strings are passed to functions as memory addresses and the `println!` macro desugars to calls to functions within `core::fmt`. 
 </usize></fn></usize></usize>