diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c index 8e44870..f6211a0 100644 --- a/arch/x86/net/bpf_jit_comp.c +++ b/arch/x86/net/bpf_jit_comp.c @@ -11,6 +11,7 @@ #include #include #include +#include /* * Conventions : @@ -48,13 +49,87 @@ static inline u8 *emit_code(u8 *ptr, u32 bytes, unsigned int len) return ptr + len; } +#ifdef CONFIG_GRKERNSEC_JIT_HARDEN +#define MAX_INSTR_CODE_SIZE 96 +#else +#define MAX_INSTR_CODE_SIZE 64 +#endif + #define EMIT(bytes, len) do { prog = emit_code(prog, bytes, len); } while (0) #define EMIT1(b1) EMIT(b1, 1) #define EMIT2(b1, b2) EMIT((b1) + ((b2) << 8), 2) #define EMIT3(b1, b2, b3) EMIT((b1) + ((b2) << 8) + ((b3) << 16), 3) #define EMIT4(b1, b2, b3, b4) EMIT((b1) + ((b2) << 8) + ((b3) << 16) + ((b4) << 24), 4) + +#ifdef CONFIG_GRKERNSEC_JIT_HARDEN +/* original constant will appear in ecx */ +#define DILUTE_CONST_SEQUENCE(_off, _key) \ +do { \ + /* mov ecx, randkey */ \ + EMIT1(0xb9); \ + EMIT(_key, 4); \ + /* xor ecx, randkey ^ off */ \ + EMIT2(0x81, 0xf1); \ + EMIT((_key) ^ (_off), 4); \ +} while (0) + +#define EMIT1_off32(b1, _off) \ +do { \ + switch (b1) { \ + case 0x05: /* add eax, imm32 */ \ + case 0x2d: /* sub eax, imm32 */ \ + case 0x25: /* and eax, imm32 */ \ + case 0x0d: /* or eax, imm32 */ \ + case 0xb8: /* mov eax, imm32 */ \ + case 0x3d: /* cmp eax, imm32 */ \ + case 0xa9: /* test eax, imm32 */ \ + DILUTE_CONST_SEQUENCE(_off, randkey); \ + EMIT2((b1) - 4, 0xc8); /* convert imm instruction to eax, ecx */\ + break; \ + case 0xbb: /* mov ebx, imm32 */ \ + DILUTE_CONST_SEQUENCE(_off, randkey); \ + /* mov ebx, ecx */ \ + EMIT2(0x89, 0xcb); \ + break; \ + case 0xbe: /* mov esi, imm32 */ \ + DILUTE_CONST_SEQUENCE(_off, randkey); \ + /* mov esi, ecx */ \ + EMIT2(0x89, 0xce); \ + break; \ + case 0xe9: /* jmp rel imm32 */ \ + EMIT1(b1); \ + EMIT(_off, 4); \ + /* prevent fall-through, we're not called if off = 0 */ \ + EMIT(0xcccccccc, 4); \ + EMIT(0xcccccccc, 4); \ + break; \ + default: \ + EMIT1(b1); \ + EMIT(_off, 4); \ + } \ +} while (0) + +#define EMIT2_off32(b1, b2, _off) \ +do { \ + if ((b1) == 0x8d && (b2) == 0xb3) { /* lea esi, [rbx+imm32] */ \ + EMIT2(0x8d, 0xb3); /* lea esi, [rbx+randkey] */ \ + EMIT(randkey, 4); \ + EMIT2(0x8d, 0xb6); /* lea esi, [esi+off-randkey] */ \ + EMIT((_off) - randkey, 4); \ + } else if ((b1) == 0x69 && (b2) == 0xc0) { /* imul eax, imm32 */\ + DILUTE_CONST_SEQUENCE(_off, randkey); \ + /* imul eax, ecx */ \ + EMIT3(0x0f, 0xaf, 0xc1); \ + } else { \ + EMIT2(b1, b2); \ + EMIT(_off, 4); \ + } \ +} while (0) +#else #define EMIT1_off32(b1, off) do { EMIT1(b1); EMIT(off, 4);} while (0) +#define EMIT2_off32(b1, b2, off) do { EMIT2(b1, b2); EMIT(off, 4);} while (0) +#endif #define CLEAR_A() EMIT2(0x31, 0xc0) /* xor %eax,%eax */ #define CLEAR_X() EMIT2(0x31, 0xdb) /* xor %ebx,%ebx */ @@ -89,6 +164,24 @@ do { \ #define X86_JBE 0x76 #define X86_JA 0x77 +#ifdef CONFIG_GRKERNSEC_JIT_HARDEN +#define APPEND_FLOW_VERIFY() \ +do { \ + /* mov ecx, randkey */ \ + EMIT1(0xb9); \ + EMIT(randkey, 4); \ + /* cmp ecx, randkey */ \ + EMIT2(0x81, 0xf9); \ + EMIT(randkey, 4); \ + /* jz after 8 int 3s */ \ + EMIT2(0x74, 0x08); \ + EMIT(0xcccccccc, 4); \ + EMIT(0xcccccccc, 4); \ +} while (0) +#else +#define APPEND_FLOW_VERIFY() do { } while (0) +#endif + #define EMIT_COND_JMP(op, offset) \ do { \ if (is_near(offset)) \ @@ -96,6 +189,7 @@ do { \ else { \ EMIT2(0x0f, op + 0x10); \ EMIT(offset, 4); /* jxx .+off32 */ \ + APPEND_FLOW_VERIFY(); \ } \ } while (0) @@ -130,7 +224,7 @@ struct bpf_jit_work { void bpf_jit_compile(struct sk_filter *fp) { - u8 temp[64]; + u8 temp[MAX_INSTR_CODE_SIZE]; u8 *prog; unsigned int proglen, oldproglen = 0; int ilen, i; @@ -143,6 +237,9 @@ void bpf_jit_compile(struct sk_filter *fp) unsigned int *addrs; const struct sock_filter *filter = fp->insns; int flen = fp->len; +#ifdef CONFIG_GRKERNSEC_JIT_HARDEN + unsigned int randkey; +#endif if (!bpf_jit_enable) return; @@ -155,11 +252,15 @@ void bpf_jit_compile(struct sk_filter *fp) if (!fp->work) goto out; +#ifdef CONFIG_GRKERNSEC_JIT_HARDEN + randkey = get_random_int(); +#endif + /* Before first pass, make a rough estimation of addrs[] - * each bpf instruction is translated to less than 64 bytes + * each bpf instruction is translated to less than MAX_INSTR_CODE_SIZE bytes */ for (proglen = 0, i = 0; i < flen; i++) { - proglen += 64; + proglen += MAX_INSTR_CODE_SIZE; addrs[i] = proglen; } cleanup_addr = proglen; /* epilogue address */ @@ -267,10 +368,8 @@ void bpf_jit_compile(struct sk_filter *fp) case BPF_S_ALU_MUL_K: /* A *= K */ if (is_imm8(K)) EMIT3(0x6b, 0xc0, K); /* imul imm8,%eax,%eax */ - else { - EMIT2(0x69, 0xc0); /* imul imm32,%eax */ - EMIT(K, 4); - } + else + EMIT2_off32(0x69, 0xc0, K); /* imul imm32,%eax */ break; case BPF_S_ALU_DIV_X: /* A /= X; */ seen |= SEEN_XREG; @@ -290,8 +389,14 @@ void bpf_jit_compile(struct sk_filter *fp) EMIT4(0x31, 0xd2, 0xf7, 0xf3); /* xor %edx,%edx; div %ebx */ break; case BPF_S_ALU_DIV_K: /* A = reciprocal_divide(A, K); */ +#ifdef CONFIG_GRKERNSEC_JIT_HARDEN + DILUTE_CONST_SEQUENCE(K, randkey); + // imul rax, rcx + EMIT4(0x48, 0x0f, 0xaf, 0xc1); +#else EMIT3(0x48, 0x69, 0xc0); /* imul imm32,%rax,%rax */ EMIT(K, 4); +#endif EMIT4(0x48, 0xc1, 0xe8, 0x20); /* shr $0x20,%rax */ break; case BPF_S_ALU_AND_X: @@ -518,8 +623,7 @@ common_load_ind: seen |= SEEN_DATAREF | SEEN_XREG; if (is_imm8(K)) { EMIT3(0x8d, 0x73, K); /* lea imm8(%rbx), %esi */ } else { - EMIT2(0x8d, 0xb3); /* lea imm32(%rbx),%esi */ - EMIT(K, 4); + EMIT2_off32(0x8d, 0xb3, K); /* lea imm32(%rbx),%esi */ } } else { EMIT2(0x89,0xde); /* mov %ebx,%esi */ diff --git a/grsecurity/Kconfig b/grsecurity/Kconfig index d38b430..10c36fb 100644 --- a/grsecurity/Kconfig +++ b/grsecurity/Kconfig @@ -60,6 +60,23 @@ config GRKERNSEC_IO IF YOU USE XFree86. If you use XFree86 and you still want to protect your kernel against modification, use the RBAC system. +config GRKERNSEC_JIT_HARDEN + bool "Harden BPF JIT against spray attacks" + default y if GRKERNSEC_CONFIG_AUTO + depends on BPF_JIT + help + If you say Y here, the native code generated by the kernel's Berkeley + Packet Filter (BPF) JIT engine will be hardened against JIT-spraying + attacks that attempt to fit attacker-beneficial instructions in + 32bit immediate fields of JIT-generated native instructions. The + attacker will generally aim to cause an unintended instruction sequence + of JIT-generated native code to execute by jumping into the middle of + a generated instruction. This feature effectively randomizes the 32bit + immediate constants present in the generated code to thwart such attacks. + + If you're using KERNEXEC, it's recommended that you enable this option + to supplement the hardening of the kernel. + config GRKERNSEC_PROC_MEMMAP bool "Harden ASLR against information leaks and entropy reduction" default y if (GRKERNSEC_CONFIG_AUTO || PAX_NOEXEC || PAX_ASLR)