home
✍🏻

Solana rBPF Vulnerability Case Study

Article realeased on Dec 8, 2022

Introduction

The Extended Berkeley Packet Filter (eBPF) was originally created to filter packets in the kernel.
Currently, eBPF is used in various areas such as Application Profiling due to its efficiency and scalability.
Considering these advantages, when Smart Contracts running in Solana are compiled, they are converted into eBPF Bytecode, and a separate virtual machine exists to execute them.
According to the Audit Report (https://solana.com/solana-security-audit-2019.pdf) issued by Kudelski Security in 2019, rBPF Virtual Machine was not an audit scope, and no audits for rBPF were seen after that.
So, among the attack surfaces that exist in Solana, I thought it was the point where zero day vulnerabilities can be found with the highest probability, and I document what I had learned before looking for bugs.

Introduction

The Extended Berkeley Packet Filter (eBPF) was originally created to filter and monitor packets in the kernel.
Because of its efficiency and scalability, eBPF is being employed in a variety of fields, including application profiling.
Given these advantages, when Solana Smart Contracts are compiled, they are converted into eBPF Programs, and a separate virtual machine is created to execute them.
As such, the rBPF VM exists in Solana, much like EVM exists in Solidity-based blockchains. (https://crates.io/crates/solana_rbpf)
According to the Audit Report (https://solana.com/solana-security-audit-2019.pdf) issued by Kudelski Security in 2019, rBPF was not an audit scope, and no audits for rBPF were seen after that.
So, among the attack surfaces that exist in Solana, I thought it was the point where zero day vulnerabilities can be found with the highest probability, and I documented what I had learned before looking for bugs.

What is eBPF? rBPF?

The eBPF (Extended Berkley packet filter) was developed to filter packets, but it provides a generic and flexible instruction sets that allows it to do more than just a filter.
It is also used as an element for executing Smart Contracts in Solana, and other outstanding use cases include networking, tracing, and security.
Solana uses rBPF (BPF Virtual Machine written in Rust) to obtain state by executing eBPF Bytecode at the application level.
eBPF is basically a generic purpose RISC instruction set, and in Solana, code written in Rust is converted into BPF bytecode form through LLVM during the compilation process.
The converted BPF Bytecode is stored in a separate account, and when a specific Smart Contract is executed, the account is referred to.
The overall process is shown in the figure below.
Before delving into rBPF vulnerability cases, let me discuss the BPF Instruction Set briefly.
In solana-rBPF, Virtual Machine have 11 64-bit registers, pc, and user-defined size stack, and the registers are named r0-r10.
Instruction is composed as shown in the image above, and Instruction does not necessarily use all fields.
In this case, unused fields are filled with zeros.
A detailed specification of Instruction Set can be found at https://docs.kernel.org/bpf/instruction-set.html.
eBPF has the advantage of being able to run the instructions safely, which means that the eBPF program effectively prevents crashes that can occur for various reasons, such as memory access to wrong addresses.
To ensure the safety of eBPF, rBPF execute separate verifier and it checks the eBPF Program provided as an input.
However, unlike the original kernel, it operates at the application level, so there is a difference during the verification process.
It is possible that this difference could lead to vulnerabilities that would not have existed in Kernel space.
Let's take an example of the difference. Kernel checks whether the BPF program is a directed acyclic graph (DAG), but the rBPF Virtual Machine adopted by solana does not check such a part.
Rather, rBPF Virtual Machine set the Maximum Instruction Count and check whether or not the Maximum Instruction Count is exceeded by counting each time the Instruction is executed. if the count exceeds the maximum number of instructions, Error is occurred.
As in the example above, there are parts that are verified using different method by the different verifier, and this part will also be verified whether is vulnerable in the future.
Since eBPF has been created for a very long time, many vulnerabilities have been discovered, and security has been greatly improved during long time.
However, there are many changed parts from the existing eBPF Virtual Machine to the rBPF created for application level and the solana-rBPF customized for use in Solana, so I think that these parts can lead to vulnerabilities.

rBPF Attack Vector that can lead to vulnerability

Vulnerability can occur in solana-rBPF as follows.
Differences between eBPF Virtual Machine rBPF Virtual Machine Solana-rBPF Virtual Machine that can lead to unexpected results
Input data that derives different outputs between JIT Mode and Interpreter Mode that can lead to Network Fork
Input data such as ELF header that can lead to out-of-bounds during ELF parsing process
3rd-party Library used by solana-rBPF Virtual Machine.
Now, let's take a look at the 1-Day vulnerability that occurred in the attack surface mentioned above.

rBPF 1-Day Vulnerability Case Study

CVE-2021-46102

This vulnerability was found by the BlockSec Team.
if solana smart contract developer compile the code for the Solana contract in Rust or C, a file in the form of .so with the elf format is created.
When deploying a contract through the BPF Loader and so on, the .so file is uploaded and entered into the elf parser in rBPF first. (load function of “elf.rs”)
.so file can be enough user input to trigger a vulnerability during the elf parsing process.
Let’s see at the process where the vulnerability occurs.
This vulnerability occurred during the process of parsing the elf header.
Below is the code that the vulnerability occurred
fn relocate( config: &Config, bpf_functions: &mut BTreeMap<u32, (usize, String)>, syscall_symbols: &mut BTreeMap<u32, String>, syscall_registry: &mut SyscallRegistry, elf: &Elf, elf_bytes: &mut [u8], ) -> Result<(), ElfError> { ... // Fixup all the relocations in the relocation section if exists for relocation in &elf.dynrels { let r_offset = relocation.r_offset as usize; // Offset of the immediate field let imm_offset = r_offset.saturating_add(BYTE_OFFSET_IMMEDIATE); match BpfRelocationType::from_x86_relocation_type(relocation.r_type) { Some(BpfRelocationType::R_Bpf_64_64) => { ... let addr = (sym.st_value + refd_pa) as u64; ... } } ... }
Rust
The code that vulnerability occurred performs parsing to get the necessary information to arrange and relocate the symbol definition and reference of the program in the Symbol Table Entry.
Let's see the structure of the original Symbol Table Entry used parsing.
/* 64-bit ELF base types. */ typedef __u64 Elf64_Addr; typedef __u16 Elf64_Half; typedef __s16 Elf64_SHalf; typedef __u64 Elf64_Off; typedef __s32 Elf64_Sword; typedef __u32 Elf64_Word; typedef __u64 Elf64_Xword; typedef __s64 Elf64_Sxword; ... typedef struct elf64_sym { Elf64_Word st_name; /* Symbol name, index in string tbl */ unsigned char st_info; /* Type and binding attributes */ unsigned char st_other; /* No defined meaning, 0 */ Elf64_Half st_shndx; /* Associated section index */ Elf64_Addr st_value; /* Value of the symbol */ Elf64_Xword st_size; /* Associated symbol size */ } Elf64_Sym;
C
Both sym.st_value and refd_pa values are user input. Therefore, if these two values are added, it becomes larger than the maximum value of the given type and can cause integer overflow.
it is best practice to prevent overflow and underflow using saturating_add in Rust. but it seems to catch the part that did not use safe function that provide by Rust.
After a vulnerability was discovered, functions provided by rust were added to the code to perform safe arithmetic operations. (saturating_add, saturating_sub, saturating_mul, checked_div, checked_shr, checked_rem, checked_add) The patched code can prevent the same type of vulnerability.
files
236

CVE-2022-23066

This vulnerability is also a vulnerability discovered by the Blocksec Team. Unlike CVE-2021-46102, it is a vulnerability that can cause Network Fork by making the state between the JIT Mode Node and the Interpreter Mode Node different.
As mentioned above, rBPF Virtual Machine can select JIT mode option and Interpreter mode option.
The validator can select and execute incoming eBPF Bytecode among two modes.
If the result of executing eBPF Bytecode in JIT Mode and the result of executing eBPF Bytecode in Intepreter Mode are different, Network Fork can be caused because the state stored in each node is different.
I wonder why there is not only one Intepreter Mode in terms of minimizing the attack surface.
The vulnerable instruction that sdiv32 supports signed division on operands.
The problem with the sdiv32 instruction is that the calculation result for 32 bits is stored in a 64-bit register, but sign extension is not performed when the calculation result for 32 bits is stored in a 64-bit register.
In the code below, sign extension is applied only to the MUL opcode.
On the other hand, code extension is performed in Interpreter Mode.
// JIT Mode // pub const BPF_ALU_OP_MASK: u8 = 0xf0; // pub const BPF_MUL: u8 = 0x20; if size == OperandSize::S32 && opc & ebpf::BPF_ALU_OP_MASK == ebpf::BPF_MUL { X86Instruction::sign_extend_i32_to_i64(dst, dst).emit(jit)?; }
Rust
Suppose we divide 12 by -4 using sdiv32.
In Interpreter Mode, 0xFFFFFFFFFFFFFFFD(-3) is stored in 64-bit register. On the other hand, in JIT Mode, 0x00000000FFFFFFFD is stored in a 64-bit register.
In JIT Mode, it is recognized as a positive number, not a negative number.
so as a result, the results of Interpreter Mode and JIT Mode are different, so Network Fork can occur.
Afterwards, the code extension applied only to the MUL opcode was applied to the SDIV opcode to prevent the vulnerability.

CVE-2022-31264

This is a vulnerability discovered by Ainevsia.
It looks similar to CVE-2021-46102, but the location of the vulnerable code is different.
It does not occur because of the rBPF Virtual Machine code, but it occurs in the library, "goblin" for Elf Parsing in the rBPF Virtual Machine.
// https://github.com/m4b/goblin/blob/413f8fc9e7da2f5b01bd82cf939f4b1e0841d807/src/elf/program_header.rs /// Returns this program header's virtual memory range pub fn vm_range(&self) -> Range<usize> { self.p_vaddr as usize..self.p_vaddr as usize + self.p_memsz as usize }
Rust
It is presumed that the developer overlooked the fact that the sum of ELF Virtual Address and Memsize becomes larger than the maximum value of usize type.
This case showed that the library used in rBPF could also be used as an attack vector.
Afterwards, goblin library where the vulnerability occurred was updated to remove the vulnerability using the saturating_add function, and the vulnerability was completely removed by including that part in rbpf 0.2.29.

Conclusion

In this post, we learned what rBPF is for running smart contracts in Solana, which parts are vulnerable, and the 1-Day vulnerabilities accordingly.
We believe that the possibility of vulnerabilities occurring in rBPF is very high as an attack surface that has not been formally audited.
We will continue to research vulnerabilities in Solana and contribute to the Solana Community
About KALOS
KALOS is a flagship service of HAECHI LABS, the leader of the global blockchain industry. We bring together the best Web2 and Web3 experts. Security Researchers with expertise in cryptography, leaders of the global best hacker team, and blockchain/smart contract experts are responsible for securing your Web3 service.
We have secured over $60b worth of crypto assets across 400+ global crypto projects — L1/L2 projects, defi protocols, P2E games, and bridges — notably 1inch, SushiSwap, Badger DAO, SuperRare, Klaytn and Chainsafe. KALOS is the only blockchain technology company selected for the Samsung Electronics Startup Incubation Program in recognition of our expertise. We have also received technology grants from the Ethereum Foundation and Ethereum Community Fund.
Secure your smart contracts with KALOS.
Email: audit@kalos.xyz
Official website: https://kalos.xyz