🐌

RustでのPIE(Position Independent Executable)

2023/08/08に公開

この記事でstatic-pieを知ってから、いろいろPIEについて気になっています。

先に結論

Rustではダイナミックリンクの実行ファイルを作る時には基本的にPIEになります。スタティックリンクの実行ファイルの場合には限られたターゲットでのみPIEになります。

Rustのソースコードを調べると、static-pieになるのはstatic_position_independent_executablesのフラグがtrueにセットされるターゲットのみです。それは1.71.1のタグの時点では以下の通り。

$ git switch -c v1.71.1 1.71.1
$ git grep static_position_independent_executables.*true
compiler/rustc_target/src/spec/hermit_base.rs:        static_position_independent_executables: true,
compiler/rustc_target/src/spec/nto_qnx_base.rs:        static_position_independent_executables: true,
compiler/rustc_target/src/spec/s390x_unknown_linux_musl.rs:    base.static_position_independent_executables = true;
compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs:    base.static_position_independent_executables = true;
compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs:    base.static_position_independent_executables = true;
compiler/rustc_target/src/spec/x86_64_unknown_none.rs:        static_position_independent_executables: true,

RustでのPIE

この記事で書いた自分のプロセスのメモリマップをダンプするプログラムのRust版を書きました。

src/main.rs
use std::fs::File;
use std::io::{self};

fn dump_mem_maps(out: &mut dyn io::Write) -> io::Result<()> {
    let mut maps = File::open("/proc/self/maps")?;
    io::copy(&mut maps, out)?;
    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    dump_mem_maps(&mut io::stderr())?;
    Ok(())
}

RustではデフォルトのビルドでPIEができます。

$ cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
$ file target/debug/memmaps-rs
target/debug/memmaps-rs: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=84c07c162ce200e14d261f8a13cef4f4c2d94d1e, for GNU/Linux 3.7.0, with debug_info, not stripped
$ target/debug/memmaps-rs
aaaab0be0000-aaaab0c2b000 r-xp 00000000 08:01 813412                     /home/koba/work/rust/memmap-rs/target/debug/memmaps-rs
aaaab0c3b000-aaaab0c3e000 r--p 0004b000 08:01 813412                     /home/koba/work/rust/memmap-rs/target/debug/memmaps-rs
aaaab0c3e000-aaaab0c3f000 rw-p 0004e000 08:01 813412                     /home/koba/work/rust/memmap-rs/target/debug/memmaps-rs
aaaaedc1b000-aaaaedc3c000 rw-p 00000000 00:00 0                          [heap]
ffffb9670000-ffffb97f9000 r-xp 00000000 08:01 10226                      /usr/lib/aarch64-linux-gnu/libc.so.6
ffffb97f9000-ffffb9808000 ---p 00189000 08:01 10226                      /usr/lib/aarch64-linux-gnu/libc.so.6
ffffb9808000-ffffb980c000 r--p 00188000 08:01 10226                      /usr/lib/aarch64-linux-gnu/libc.so.6
ffffb980c000-ffffb980e000 rw-p 0018c000 08:01 10226                      /usr/lib/aarch64-linux-gnu/libc.so.6
ffffb980e000-ffffb981a000 rw-p 00000000 00:00 0 
ffffb9820000-ffffb9834000 r-xp 00000000 08:01 50874                      /usr/lib/aarch64-linux-gnu/libgcc_s.so.1
ffffb9834000-ffffb9843000 ---p 00014000 08:01 50874                      /usr/lib/aarch64-linux-gnu/libgcc_s.so.1
ffffb9843000-ffffb9844000 r--p 00013000 08:01 50874                      /usr/lib/aarch64-linux-gnu/libgcc_s.so.1
ffffb9844000-ffffb9845000 rw-p 00014000 08:01 50874                      /usr/lib/aarch64-linux-gnu/libgcc_s.so.1
ffffb985f000-ffffb988a000 r-xp 00000000 08:01 4871                       /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffffb988d000-ffffb988e000 ---p 00000000 00:00 0 
ffffb988e000-ffffb9896000 rw-p 00000000 00:00 0 
ffffb9896000-ffffb9898000 r--p 00000000 00:00 0                          [vvar]
ffffb9898000-ffffb9899000 r-xp 00000000 00:00 0                          [vdso]
ffffb9899000-ffffb989b000 r--p 0002a000 08:01 4871                       /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffffb989b000-ffffb989d000 rw-p 0002c000 08:01 4871                       /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffffed407000-ffffed428000 rw-p 00000000 00:00 0                          [stack]

もう一度実行するとメモリマップの配置が変わっているのがわかります。

$ target/debug/memmaps-rs
aaaac8e50000-aaaac8e9b000 r-xp 00000000 08:01 813412                     /home/koba/work/rust/memmap-rs/target/debug/memmaps-rs
aaaac8eab000-aaaac8eae000 r--p 0004b000 08:01 813412                     /home/koba/work/rust/memmap-rs/target/debug/memmaps-rs
aaaac8eae000-aaaac8eaf000 rw-p 0004e000 08:01 813412                     /home/koba/work/rust/memmap-rs/target/debug/memmaps-rs
aaaae63a3000-aaaae63c4000 rw-p 00000000 00:00 0                          [heap]
ffff80aa0000-ffff80c29000 r-xp 00000000 08:01 10226                      /usr/lib/aarch64-linux-gnu/libc.so.6
ffff80c29000-ffff80c38000 ---p 00189000 08:01 10226                      /usr/lib/aarch64-linux-gnu/libc.so.6
ffff80c38000-ffff80c3c000 r--p 00188000 08:01 10226                      /usr/lib/aarch64-linux-gnu/libc.so.6
ffff80c3c000-ffff80c3e000 rw-p 0018c000 08:01 10226                      /usr/lib/aarch64-linux-gnu/libc.so.6
ffff80c3e000-ffff80c4a000 rw-p 00000000 00:00 0 
ffff80c50000-ffff80c64000 r-xp 00000000 08:01 50874                      /usr/lib/aarch64-linux-gnu/libgcc_s.so.1
ffff80c64000-ffff80c73000 ---p 00014000 08:01 50874                      /usr/lib/aarch64-linux-gnu/libgcc_s.so.1
ffff80c73000-ffff80c74000 r--p 00013000 08:01 50874                      /usr/lib/aarch64-linux-gnu/libgcc_s.so.1
ffff80c74000-ffff80c75000 rw-p 00014000 08:01 50874                      /usr/lib/aarch64-linux-gnu/libgcc_s.so.1
ffff80c87000-ffff80cb2000 r-xp 00000000 08:01 4871                       /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffff80cb5000-ffff80cb6000 ---p 00000000 00:00 0 
ffff80cb6000-ffff80cbe000 rw-p 00000000 00:00 0 
ffff80cbe000-ffff80cc0000 r--p 00000000 00:00 0                          [vvar]
ffff80cc0000-ffff80cc1000 r-xp 00000000 00:00 0                          [vdso]
ffff80cc1000-ffff80cc3000 r--p 0002a000 08:01 4871                       /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffff80cc3000-ffff80cc5000 rw-p 0002c000 08:01 4871                       /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffffe94f6000-ffffe9517000 rw-p 00000000 00:00 0                          [stack]

今度はmuslのライブラリを使ってビルドしてみます。

$ cargo build --target aarch64-unknown-linux-musl
   Compiling memmaps-rs v0.1.0 (/home/koba/work/rust/memmap-rs)
    Finished dev [unoptimized + debuginfo] target(s) in 0.51s
$ file target/aarch64-unknown-linux-musl/debug/memmaps-rs
target/aarch64-unknown-linux-musl/debug/memmaps-rs: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, BuildID[sha1]=f9fa314e2db82ca61654144ca7f74ce940aa1a65, with debug_info, not stripped

muslを使うとデフォルトでスタティックリンクになります。でもaarch64ではstatic-pieにはなりませんでした。

$ cargo build --target x86_64-unknown-linux-musl
   Compiling memmaps-rs v0.1.0 (/home/koba/work/rust/memmap-rs)
    Finished dev [unoptimized + debuginfo] target(s) in 0.60s
$ file target/x86_64-unknown-linux-musl/debug/memmaps-rs
target/x86_64-unknown-linux-musl/debug/memmaps-rs: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), static-pie linked, with debug_info, not stripped

x86_64-unknown-linux-musl向けにビルドするとこのようにstatic-pie になりました。

余談

binfmt_miscのしくみのおかげで、aarch64のlinuxの上でもx86_64-unknown-linux-musl向けの実行ファイルを動かすことができます。

$ ./target/x86_64-unknown-linux-musl/debug/memmaps-rs 
4000000000-4000063000 r--p 00000000 08:01 813298                         /home/koba/work/rust/memmap-rs/target/x86_64-unknown-linux-musl/debug/memmaps-rs
4000063000-4000064000 ---p 00000000 00:00 0                              
4000064000-4000070000 rw-p 00063000 08:01 813298                         /home/koba/work/rust/memmap-rs/target/x86_64-unknown-linux-musl/debug/memmaps-rs
4000070000-4000071000 rw-p 00000000 00:00 0                              
4000071000-4000072000 ---p 00000000 00:00 0                              
4000072000-4000073000 rw-p 00000000 00:00 0                              
4001071000-4001072000 ---p 00000000 00:00 0                              
4001072000-4001874000 rw-p 00000000 00:00 0                              [stack]
4001874000-4001875000 ---p 00000000 00:00 0                              
4001875000-4001878000 rw-p 00000000 00:00 0                              
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]

もう一度実行。

$ ./target/x86_64-unknown-linux-musl/debug/memmaps-rs 
4000000000-4000063000 r--p 00000000 08:01 813298                         /home/koba/work/rust/memmap-rs/target/x86_64-unknown-linux-musl/debug/memmaps-rs
4000063000-4000064000 ---p 00000000 00:00 0                              
4000064000-4000070000 rw-p 00063000 08:01 813298                         /home/koba/work/rust/memmap-rs/target/x86_64-unknown-linux-musl/debug/memmaps-rs
4000070000-4000071000 rw-p 00000000 00:00 0                              
4000071000-4000072000 ---p 00000000 00:00 0                              
4000072000-4000073000 rw-p 00000000 00:00 0                              
4001071000-4001072000 ---p 00000000 00:00 0                              
4001072000-4001874000 rw-p 00000000 00:00 0                              [stack]
4001874000-4001875000 ---p 00000000 00:00 0                              
4001875000-4001878000 rw-p 00000000 00:00 0                              
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]

アドレスの配置が変わりません。これはqemuを通して実行しているので、ロードアドレスをランダム化しているところを通過しないためですね。もちろん、この実行ファイルをx86_64のLinuxに持って行って動かせば、ロードアドレスのランダム化が効きます。

関連

https://zenn.dev/tetsu_koba/articles/ecde4c6b5073bd
https://zenn.dev/tetsu_koba/articles/dc87d3c86c8d50

Discussion