Transpiling A Kernel Module to Rust: The Good, the Bad and the Ugly

栏目: IT技术 · 发布时间: 3年前

内容简介:Earlier this year, we used the C2Rust framework to translate applications such asQuake 3 to Rust. In this post, we’ll show you that it is also possible to translate privileged software such as modules that are loaded by the Linux kenel. We’ll use a small,

Earlier this year, we used the C2Rust framework to translate applications such asQuake 3 to Rust. In this post, we’ll show you that it is also possible to translate privileged software such as modules that are loaded by the Linux kenel. We’ll use a small, 3-file kernel module which is part of the Bareflank Hypervisor SDK developed by Assured Information Security but you can use the same techniques to translate other kernel modules. The basic steps we’ll cover are:

  • translating the C files into Rust,
  • compiling generated Rust, and
  • linking everything into a loadable kernel module.

The Good

We transpiled the kernel module of Bareflank which consists of a small number of C files: common.c , entry.c and platform.c . For the first step, we needed to extract the kernel’s compilation flags and pass them to the transpiler, so we would perfectly replicate the configuration settings and macro expansions from the kernel build. We used the Bear tool to pseudo-build the original kernel module and produce a compile_commands.json file containing the compilation database:

$ cd .../bareflank/bfdriver/src/platform/linux
$ make clean
$ bear make CC=clang

The kernel build system compiles C files differently (enabling or disabling some language features) depending on the compiler used. Since the C2Rust transpiler uses clang as its front-end, we had to use clang for the pseudo-build as well. One complication we ran into were asm gotos , which are a new extension to gcc’s inline assembly syntax that Linux started using in the 5.0 release, but were added to LLVM/clang in version 9.0. When we initially performed this experiment (April 2019), that version of clang hadn’t come out so we were forced to revert to an older 4.x kernel that did not use asm gotos just to get clang to parse the C code. Asm gotos are now supported by clang, but the transpiler doesn’t support them yet. We need to first investigate whether Rust’s inline assembly (either the legacy version or the recently added redesign ) even supports this feature right now.

After producing the compilation database, we transpiled the C files into Rust:

$ # Remove compiler flags we can't handle
$ sed -i -e 's/"-Werror.*",//' ./compile_commands.json
$ c2rust transpile ./compile_commands.json --emit-no-std \
  --emit-modules -o bareflank-rs "<a href="/cdn-cgi/l/email-protection" data-cfemail="0c284c">[email protected]</a>"

After making some fixes and additions to C2Rust and fix the subsequent compilation errors, we compiled the resulting Rust code with:

# cargo build --release --target x86_64-linux-kernel -Zbuild-std=core

We had originally used cargo-xbuild to accomplish this without support from the Rust compiler, but the Rust team added the kernel module target to Cargo at the end of August 2019. Once that happened, we could use cargo build directly to build our kernel module.

We had to make a few minor manual changes to the Rust files:

  • Replace use libc; with use crate::libc; since we had some problems using the libc crate from crates.io inside a kernel module. C2Rust uses the basic C type definitions from this crate, and we had to redefine them manually. This may not be a problem anymore.

  • Manually remove a couple of duplicate current_stack_pointer definitions. This variable is defined in a header in C and the transpiler emits it in every transpiled .rs file as #[no_mangle] , which is a Rust error. We had to manually remove all but one definitions of this symbol (the symbol wasn’t even used anywhere, so we could have removed all of them, but didn’t need to).

Once all the fixes were in, we successfully compiled the module but still had to fix a couple of issues. First, Cargo produces a static library called libbareflank_rs.a , but the kernel build system only supports object files and not static libraries. We turned one into the other by calling the system linker from our kernel module Makefile :

$(M)/libbareflank_rs.o: target/x86_64-linux-kernel/release/bareflank_rs.a
        $(LD) -r -o <a href="/cdn-cgi/l/email-protection" data-cfemail="d1f591">[email protected]</a> --whole-archive $^

The second issue was that we were initially also transpiling the bareflank.mod.c file that the kernel build system auto-generates from the input object files. We were producing a libbareflank_rs.o that already included a copy of it, then passing that to the kernel build system that would produce a second bareflank.mod.c and link that together with libbareflank_rs.o to produce bareflank.ko . Having two copies of the contents of bareflank.mod.c was causing crashes. Once we removed the transpiled bareflank.mod.rs file from the build, the kernel module loaded and ran successfully.

The Bad

We encountered many C features and gcc-specific extensions that the kernel headers use and we had to add to C2Rust:

  • A series of gcc attributes: inline , cold , alias , used , section and more

  • Updates to the C2Rust bitfields crate: support for no_std and booleans

  • A gcc intrinsic used by some kernel memory functions: __builtin_object_size

  • Inline assembly support in C2Rust for memory-only, read-write and early-clobber operands

  • Structures that are both packed and aligned, mainly xregs_state . This particular combination is not currently supported in Rust, so we had to implement a work-around in C2Rust by emitting a pair of nested structures (aligned outer structure, packed inner structure) instead:

#[repr(C, align(64))]
pub struct xregs_state(pub xregs_state_Inner);
#[repr(C, packed)]
pub struct xregs_state_Inner {
  // ...
}

All of these issues have been implemented in C2Rust and should work for all kernel modules, but different modules may expose new unimplemented C features.

The Ugly

C2Rust maps C types to their definitions in the libc crate, but we were unable (at the time) to use this crate in our kernel module. Instead, we redefined some of its types manually:

pub mod libc {
    pub type c_char = i8;
    pub type c_schar = i8;
    pub type c_ulong = u64;
    pub type c_uint = u32;
    // ...all the others
}

We also had to implement a couple of libc functions that the compiler relies on:

pub unsafe fn memset(s: *mut c_void, c: c_int, n: size_t) -> *mut c_void {
    core::ptr::write_bytes(s as *mut u8, c as u8, n as usize);
    s
}

pub unsafe fn memcpy(dest: *mut c_void, src: *const c_void, n: size_t) -> *mut c_void {
    core::ptr::copy_nonoverlapping(src as *const u8, dest as *mut u8, n as usize);
    dest
}

Finally, we added stubs for a few missing kernel functions, and for the Rust panic handler (we decided to implement it properly at a later date):

#[no_mangle]
pub extern "C" fn __bad_size_call_parameter() -> ! {
    unreachable!("__bad_size_call_parameter")
}

#[no_mangle]
pub extern "C" fn __bad_percpu_size() -> ! {
    unreachable!("__bad_percpu_size")
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    // FIXME: call into the kernel
    loop {}
}

The main remaining “ugliness” is that the transpiler fully expands C macros, so the transpiled code is not always pleasant to look at:

match ::core::mem::size_of::<libc::c_int>() as libc::c_ulong {
	1 => {
	    pscr_ret__ = ({
		let mut pfo_ret__: libc::c_int = 0;
		match ::core::mem::size_of::<libc::c_int>() as libc::c_ulong {
		    1 => asm!("movb %gs:$1,$0" :
					       "=q" (pfo_ret__) :
					       "*m" (&cpu_number)
					       : : "volatile"),
		    2 => asm!("movw %gs:$1,$0" :
					       "=r" (pfo_ret__) :
					       "*m" (&cpu_number)
					       : : "volatile"),
		    4 => asm!("movl %gs:$1,$0" :
					       "=r" (pfo_ret__) :
					       "*m" (&cpu_number)
					       : : "volatile"),
		    8 => asm!("movq %gs:$1,$0" :
					       "=r" (pfo_ret__) :
					       "*m" (&cpu_number)
					       : : "volatile"),
		    _ => {
			__bad_percpu_size();
		    }
		}
		pfo_ret__
	    })
	}

C2Rust uses clang as a front-end which currently expands macros very early before parsing, so modifying it to produce an AST with unexpanded macros would be a significant undertaking. We are currently considering other alternatives, but it is unclear whether any of them would be simpler. For the foreseeable future, C2Rust will not be able to transpile non-trivial function-like C macros to Rust macros.

We’d love to hear what you want to see translated next. Drop us a line at [email protected] and let us know! If you have legacy C/C++ code you need modernized, translated or integrated with Rust, we are here to help.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

菜鸟侦探挑战数据分析

菜鸟侦探挑战数据分析

[日] 石田基广 / 支鹏浩 / 人民邮电出版社 / 2017-1 / 42

本书以小说的形式展开,讲述了主人公俵太从大学文科专业毕业后进入征信所,从零开始学习数据分析的故事。书中以主人公就职的征信所所在的商业街为舞台,选取贴近生活的案例,将平均值、t检验、卡方检验、相关、回归分析、文本挖掘以及时间序列分析等数据分析的基础知识融入到了生动有趣的侦探故事中,讲解由浅入深、寓教于乐,没有深奥的理论和晦涩的术语,同时提供了大量实际数据,使用免费自由软件RStudio引领读者进一步......一起来看看 《菜鸟侦探挑战数据分析》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换