Rust in the Linux kernel





In  an earlier post  , Google announced that Android now supports the Rust programming language used in the development of the OS itself. In this regard, the authors of this publication also decided to evaluate how much the Rust language is in demand in the development of the Linux kernel. This post covers the technical aspects of this work with a few simple examples.



For nearly half a century, C has remained the primary language for kernel development, because C provides the degree of control and predictable performance required for such a critical component. The density of memory security bugs in the Linux kernel is usually very low because the code is very high quality, the code reviews are to strict standards, and safeguards are carefully implemented. However,  bugs related to memory safety still crop up regularly . In Android, kernel vulnerabilities are generally considered a serious flaw, as they sometimes allow the security model to be bypassed due to the kernel running in privileged mode.



Rust is supposed to have matured enough to co-operate with C as the language for the practical implementation of the kernel. Rust helps to reduce potential bugs and security vulnerabilities in privileged code, while integrating well with the core core and maintaining good performance.



Rust support



A primary prototype of  the Binder driver was developed  to adequately compare the security and performance characteristics of the existing C version and its Rust counterpart. There are over 30 million lines of code in the Linux kernel, so we do not set ourselves the goal of rewriting it entirely in Rust, but to provide the ability to add the required code in Rust. We believe this incremental approach helps leverage the high-performance implementation in the kernel, while providing kernel developers with new tools to improve memory safety and maintain performance at runtime.



We joined the Rust for Linux organization  where the community has done and continues to do a lot to add Rust support to the Linux kernel build system. We also need to design systems so that code fragments written in two languages ​​can interact with each other: we are especially interested in safe abstractions without overhead that allow Rust code to use core functionality written in C, as well as the ability to implement functionality in idiomatic Rust, which can be called smoothly from parts of the kernel written in C. 



Since Rust is a new language in the core, we also have the ability to oblige developers to adhere to best practices for documentation and consistency. For example, we have specific machine-verifiable requirements regarding the use of unsafe code: for each unsafe function, the developer must document the requirements that the caller must satisfy - thus ensuring that it is safe to use. In addition, each time an unsafe function is called, it is the developer's responsibility to document the requirements that must be met by the caller as a guarantee that use will be safe. In addition, for each call to unsafe functions (or use of unsafe constructs, for example,when dereferencing a raw pointer), the developer should document the rationale why doing so is safe.



Rust is famous not only for its security, but also for how useful and user-friendly it is for developers. Next, let's look at a few examples that demonstrate how Rust can be useful for kernel developers when writing safe and correct drivers.



Driver example



Consider the implementation of a semaphore symbolic device. Each device has an actual value; when writing n  bytes, the device value is increased by n ; with each read, this value is decreased by 1 until the value reaches 0, in which case this device is blocked until such a decrement operation can be performed on it without going below 0.



Let's say  semaphore



 this is a file representing our device. We can interact with it from the shell like this: 



> cat semaphore

      
      





When  semaphore



 is the device that has just been initialized, the command shown above is locked because the current device value is 0. It will be unlocked if we run the next command from another shell, as it will increment the value by 1, thereby allowing the original operation readout to complete:



> echo -n a > semaphore

      
      





We can also increase the counter by more than 1 if we write more data, for example:



> echo -n abc > semaphore

      
      





increases the counter by 3, so the next 3 readings will not block. 



To demonstrate some more aspects of Rust, let's add the following features to our driver: remember what the maximum value reached during the entire life cycle, and also remember how many reads each file performed on the device.



Now let's show how such a driver would be  implemented in Rust , comparing this option with the implementation in C.... However, we note that the development of this topic at Google is just beginning, and in the future everything may change. We would like to highlight how Rust can be useful to a developer in every aspect. For example, at compile time, it allows us to eliminate or greatly reduce the likelihood of entire classes of bugs creeping into the code, while keeping the code flexible and running at minimal overhead.



Character devices



The developer needs to do the following to implement the driver for the new character device in Rust:



  1. Implement a trait  FileOperations



    : All functions associated with it are optional, so the developer only needs to implement those that are relevant for the given scenario. They correspond to fields in the C structure  struct file_operations



    .
  2. Implement a trait  FileOpener



     is the type-safe equivalent of a C field  open



     from a struct  struct file_operations



    .
  3. Register a new device type for the kernel: this will tell the kernel what functions will need to be called in response to operations on files of a new type.


The following is a comparison of the first two steps in our first example in Rust and C:







Character devices in Rust have a number of security benefits:



  • File-by-file lifecycle state management:  FileOpener::open



     Returns an object whose lifetime since then is owned by the caller. Any object that implements the trait can be returned  PointerWrapper



    , and we provide implementations for 
    Box <T>
     and 
    Arc <T>
    , , Rust, , .



     FileOperations



      self



     ( ),  release



    , ( ).  release



      , - , . «» ( , ).



    , Rust , C. C , Rust, , Rust, , , . , C, Rust , Rust. , open , , , , ioctl



    /read



    /write



      ( ) ,  filp->private_data



    , ..



  • : , open



      release



       self



    , , Rust , .



    ( ),   : Mutex



      
    SpinLock



     ( 
    atomics) .



    , ( ), ( ). 





    : , , Rust . , , ,  FileOperation::open



    .  Arc, .



    ,  FileOperation




      ( , ,  open



    ,  FileOperations



    ) – .



    , , . , C miscdevice



    ,  filp->private_data



    ;  cdev



    , inode->i_cdev



    . , ,  container_of



    , . Rust .









    : , Rust , . . C , , (void



     *) : , , . Rust .





    : ,  FileOperations



    , . , impl FileOperations for Device



    ,  Device



     β€“ , ( FileState



    ). , , , . (  neovim



      LSP- rust-analyzer



     .)



    Rust, , C, struct file_operations



    . (  declare_file_operations



    ): , , ,  const



    , , .



    Ioctl 



     ioctl, ioctl



    , FileOperations



    , . 







    Ioctl , , , , , (, , , , ) . Rust  (  cmd.dispatch



    ), .



       . , , ioctl, Rust : cmd.raw



      ioctl ( , ).



    , , , - , :





    C, ( , ) ; Rust  unsafe



    , . Rust:





     



    . ; , C Rust , , , , : 







    , C, ,    Β«Β» ,  unix  ,     ,   .



    Rust:



    •  Semaphore::inner



        , ,  lock



      . , . C, , count



        max_seen



        semaphore_state



        , . there is no enforcement that the lock is held while they're accessed.
    • (RAII): , (inner



        ) . , : , , , , ; : , , , drop



      .
    • ,  Lock



      , , , Mutex



        SpinLock



      , , C, . , , , .
    • Rust , . , , . C  semaphore_consume



        Linux: , ,  mutex_unlock



         prepare_to_wait



      , . 
    • : , , , , , . , ioctl , , . Rust ,   . , C, atomic64_t



      , , . 






    ,  open



    read



     write



    :















    Rust:



    •  ?



       operator: open



        read



        Rust ; , , , . C , - . 
    • : Rust , , - . C . open



      , , C kref_get



       ( ); Rust  clone



       ( ), .
    • RAII: Rust , , inner



        , , .
    • : Rust , . write



      , , . C , , . 







    .



    10% !






All Articles