From patchwork Mon Jun 12 16:56:58 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Salvatore Mesoraca X-Patchwork-Id: 9782323 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 9051B60244 for ; Mon, 12 Jun 2017 16:59:11 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 78ED7284DA for ; Mon, 12 Jun 2017 16:59:11 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 6B8C7284DC; Mon, 12 Jun 2017 16:59:11 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED,FREEMAIL_FROM,RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 917C3284C7 for ; Mon, 12 Jun 2017 16:59:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753928AbdFLQ6l (ORCPT ); Mon, 12 Jun 2017 12:58:41 -0400 Received: from mail-wr0-f195.google.com ([209.85.128.195]:33042 "EHLO mail-wr0-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753637AbdFLQ6i (ORCPT ); Mon, 12 Jun 2017 12:58:38 -0400 Received: by mail-wr0-f195.google.com with SMTP id v104so23234864wrb.0; Mon, 12 Jun 2017 09:58:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=I1IYdFKy2cKw6XiXm99KHFOsPf5xt+pLXU1Af37uLys=; b=O8kLtjY6+Lk8a/4okeJ5k1xsCNQsQGojBzdTroPXdASnuMJfszQDOUl7vfY62ylrF3 JKQkyJLkGBq7eiamrsO3fwwzWsAstWv0TgHhadDnzxMm4NV5g5K1b8KQVIvLVlf6eAtB TpXbcYaORpPY/B+zpUz+CegcYvKlTggJ8GHMvA6tUQepMy7ta838UU7BWOTpalbvGxWi 3W4hij/6LxefV3ShTqZ9LHqtKLZBs8bp6exvK4BZFBPqGxMyRFCMXekKcBxgjN2vgcB1 wJI5K+Gzcr0BLAHwhapDlP9lir+/wFLFntAl8+Ydfc3XdF9metcXG+qp6I/+EoqA3WdN OC6g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=I1IYdFKy2cKw6XiXm99KHFOsPf5xt+pLXU1Af37uLys=; b=W8mDGXuAVF90mpXL5S5iuAfnKOUUnpLrLlCjij65PAogkMj9nCx1oTRKzb6OGiKImZ D8FBdMHjCetYoSX/qwVYBIT24I/pQT3xr4RjIGGycCsXCmGiG5H51XMuJl2a7gLqZOrc lHt8HW7T6z6wKp9yvmBHaNC/B7xxL0rFgLKMxsCNAwVEgAbrx6J4A8z0b+UcwvVCTlvw Cu0TZECTgv1qKiPU4IuWcWMFxBSh4k7UygM4mH7fvGikI7/IPvgL5Rsf18pfafoLuMrO lJQw1DdfwKmV7L0NYgZO2QsHnE1gbiNFHA5sOKNuTl2SBwABRuNznfH1NWBj2K0kmePl wJfw== X-Gm-Message-State: AKS2vOwTI6VZL1olrce7NBRb0qpw1wRU2xWYMLY3K/dFE46WchazEtQE uBwO836+afG7OE8+7/yUuxx2 X-Received: by 10.28.60.137 with SMTP id j131mr8765182wma.19.1497286709584; Mon, 12 Jun 2017 09:58:29 -0700 (PDT) Received: from localhost ([109.112.0.253]) by smtp.gmail.com with ESMTPSA id n2sm20925973wrn.30.2017.06.12.09.58.28 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 12 Jun 2017 09:58:29 -0700 (PDT) From: Salvatore Mesoraca To: linux-kernel@vger.kernel.org Cc: linux-security-module@vger.kernel.org, kernel-hardening@lists.openwall.com, Salvatore Mesoraca , Brad Spengler , PaX Team , Casey Schaufler , Kees Cook , James Morris , "Serge E. Hallyn" Subject: [PATCH 09/11] Trampoline emulation Date: Mon, 12 Jun 2017 18:56:58 +0200 Message-Id: <1497286620-15027-10-git-send-email-s.mesoraca16@gmail.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1497286620-15027-1-git-send-email-s.mesoraca16@gmail.com> References: <1497286620-15027-1-git-send-email-s.mesoraca16@gmail.com> Sender: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: X-Virus-Scanned: ClamAV using ClamSMTP Some programs need to generate part of their code at runtime. Luckily enough, in some cases they only generate well-known code sequences (the "trampolines") that can be easily recognized and emulated by the kernel. This way WX Protection can still be active, so a potential attacker won't be able to generate arbitrary sequences of code, but just those that are explicitly allowed. This is not ideal, but it's still better than having WX Protection completely disabled. In particular S.A.R.A. is able to recognize trampolines used by GCC for nested C functions and libffi's trampolines. This feature is implemented only on x86_32 and x86_64. The assembly sequences used here were originally obtained from PaX source code. Signed-off-by: Salvatore Mesoraca --- security/sara/Kconfig | 17 ++++ security/sara/include/trampolines.h | 171 ++++++++++++++++++++++++++++++++++++ security/sara/wxprot.c | 137 +++++++++++++++++++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 security/sara/include/trampolines.h diff --git a/security/sara/Kconfig b/security/sara/Kconfig index cb49f20..e91c147 100644 --- a/security/sara/Kconfig +++ b/security/sara/Kconfig @@ -137,6 +137,23 @@ choice Documentation/security/SARA.rst. endchoice +config SECURITY_SARA_WXPROT_EMUTRAMP + bool "Enable emulation for some types of trampolines" + depends on SECURITY_SARA_WXPROT + depends on X86 + default y + help + Some programs and libraries need to execute special small code + snippets from non-executable memory pages. + Most notable examples are the GCC and libffi trampolines. + This features make it possible to execute those trampolines even + if they reside in non-executable memory pages. + This features need to be enabled on a per-executable basis + via user-space utilities. + See Documentation/security/SARA.rst. for further information. + + If unsure, answer y. + config SECURITY_SARA_WXPROT_DISABLED bool "WX protection will be disabled at boot." depends on SECURITY_SARA_WXPROT diff --git a/security/sara/include/trampolines.h b/security/sara/include/trampolines.h new file mode 100644 index 0000000..eab0a85 --- /dev/null +++ b/security/sara/include/trampolines.h @@ -0,0 +1,171 @@ +/* + * S.A.R.A. Linux Security Module + * + * Copyright (C) 2017 Salvatore Mesoraca + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * Assembly sequences used here were copied from + * PaX patch by PaX Team + * + */ + +#ifndef __SARA_TRAMPOLINES_H +#define __SARA_TRAMPOLINES_H +#ifdef CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP + + +/* x86_32 */ + + +struct libffi_trampoline_x86_32 { + unsigned char mov; + unsigned int addr1; + unsigned char jmp; + unsigned int addr2; +} __packed; + +struct gcc_trampoline_x86_32_type1 { + unsigned char mov1; + unsigned int addr1; + unsigned char mov2; + unsigned int addr2; + unsigned short jmp; +} __packed; + +struct gcc_trampoline_x86_32_type2 { + unsigned char mov; + unsigned int addr1; + unsigned char jmp; + unsigned int addr2; +} __packed; + +union trampolines_x86_32 { + struct libffi_trampoline_x86_32 lf; + struct gcc_trampoline_x86_32_type1 g1; + struct gcc_trampoline_x86_32_type2 g2; +}; + +#define is_valid_libffi_trampoline_x86_32(UNION) \ + (UNION.lf.mov == 0xB8 && \ + UNION.lf.jmp == 0xE9) + +#define emulate_libffi_trampoline_x86_32(UNION, REGS) do { \ + (REGS)->ax = UNION.lf.addr1; \ + (REGS)->ip = (unsigned int) ((REGS)->ip + \ + UNION.lf.addr2 + \ + sizeof(UNION.lf)); \ +} while (0) + +#define is_valid_gcc_trampoline_x86_32_type1(UNION, REGS) \ + (UNION.g1.mov1 == 0xB9 && \ + UNION.g1.mov2 == 0xB8 && \ + UNION.g1.jmp == 0xE0FF && \ + REGS->ip > REGS->sp) + +#define emulate_gcc_trampoline_x86_32_type1(UNION, REGS) do { \ + (REGS)->cx = UNION.g1.addr1; \ + (REGS)->ax = UNION.g1.addr2; \ + (REGS)->ip = UNION.g1.addr2; \ +} while (0) + +#define is_valid_gcc_trampoline_x86_32_type2(UNION, REGS) \ + (UNION.g2.mov == 0xB9 && \ + UNION.g2.jmp == 0xE9 && \ + REGS->ip > REGS->sp) + +#define emulate_gcc_trampoline_x86_32_type2(UNION, REGS) do { \ + (REGS)->cx = UNION.g2.addr1; \ + (REGS)->ip = (unsigned int) ((REGS)->ip + \ + UNION.g2.addr2 + \ + sizeof(UNION.g2)); \ +} while (0) + + + +#ifdef CONFIG_X86_64 + +struct libffi_trampoline_x86_64 { + unsigned short mov1; + unsigned long addr1; + unsigned short mov2; + unsigned long addr2; + unsigned char stcclc; + unsigned short jmp1; + unsigned char jmp2; +} __packed; + +struct gcc_trampoline_x86_64_type1 { + unsigned short mov1; + unsigned long addr1; + unsigned short mov2; + unsigned long addr2; + unsigned short jmp1; + unsigned char jmp2; +} __packed; + +struct gcc_trampoline_x86_64_type2 { + unsigned short mov1; + unsigned int addr1; + unsigned short mov2; + unsigned long addr2; + unsigned short jmp1; + unsigned char jmp2; +} __packed; + +union trampolines_x86_64 { + struct libffi_trampoline_x86_64 lf; + struct gcc_trampoline_x86_64_type1 g1; + struct gcc_trampoline_x86_64_type2 g2; +}; + +#define is_valid_libffi_trampoline_x86_64(UNION) \ + (UNION.lf.mov1 == 0xBB49 && \ + UNION.lf.mov2 == 0xBA49 && \ + (UNION.lf.stcclc == 0xF8 || \ + UNION.lf.stcclc == 0xF9) && \ + UNION.lf.jmp1 == 0xFF49 && \ + UNION.lf.jmp2 == 0xE3) + +#define emulate_libffi_trampoline_x86_64(UNION, REGS) do { \ + (REGS)->r11 = UNION.lf.addr1; \ + (REGS)->r10 = UNION.lf.addr2; \ + (REGS)->ip = UNION.lf.addr1; \ + if (UNION.lf.stcclc == 0xF8) \ + (REGS)->flags &= ~X86_EFLAGS_CF; \ + else \ + (REGS)->flags |= X86_EFLAGS_CF; \ +} while (0) + +#define is_valid_gcc_trampoline_x86_64_type1(UNION, REGS) \ + (UNION.g1.mov1 == 0xBB49 && \ + UNION.g1.mov2 == 0xBA49 && \ + UNION.g1.jmp1 == 0xFF49 && \ + UNION.g1.jmp2 == 0xE3 && \ + REGS->ip > REGS->sp) + +#define emulate_gcc_trampoline_x86_64_type1(UNION, REGS) do { \ + (REGS)->r11 = UNION.g1.addr1; \ + (REGS)->r10 = UNION.g1.addr2; \ + (REGS)->ip = UNION.g1.addr1; \ +} while (0) + +#define is_valid_gcc_trampoline_x86_64_type2(UNION, REGS) \ + (UNION.g2.mov1 == 0xBB41 && \ + UNION.g2.mov2 == 0xBA49 && \ + UNION.g2.jmp1 == 0xFF49 && \ + UNION.g2.jmp2 == 0xE3 && \ + REGS->ip > REGS->sp) + +#define emulate_gcc_trampoline_x86_64_type2(UNION, REGS) do { \ + (REGS)->r11 = UNION.g2.addr1; \ + (REGS)->r10 = UNION.g2.addr2; \ + (REGS)->ip = UNION.g2.addr1; \ +} while (0) + +#endif /* CONFIG_X86_64 */ + +#endif /* CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP */ +#endif /* __SARA_TRAMPOLINES_H */ diff --git a/security/sara/wxprot.c b/security/sara/wxprot.c index cbeedd3..44e42be 100644 --- a/security/sara/wxprot.c +++ b/security/sara/wxprot.c @@ -20,6 +20,11 @@ #include #include +#ifdef CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP +#include +#include "include/trampolines.h" +#endif + #include "include/sara.h" #include "include/sara_data.h" #include "include/utils.h" @@ -35,6 +40,7 @@ #define SARA_WXP_COMPLAIN 0x0010 #define SARA_WXP_VERBOSE 0x0020 #define SARA_WXP_MMAP 0x0040 +#define SARA_WXP_EMUTRAMP 0x0100 #define SARA_WXP_TRANSFER 0x0200 #define SARA_WXP_NONE 0x0000 #define SARA_WXP_MPROTECT (SARA_WXP_HEAP | \ @@ -45,7 +51,12 @@ SARA_WXP_WXORX | \ SARA_WXP_COMPLAIN | \ SARA_WXP_VERBOSE) +#ifdef CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP +#define SARA_WXP_ALL (__SARA_WXP_ALL | \ + SARA_WXP_EMUTRAMP) +#else /* CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP */ #define SARA_WXP_ALL __SARA_WXP_ALL +#endif /* CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP */ struct wxprot_rule { char *path; @@ -70,7 +81,11 @@ struct wxprot_config_container { static u16 default_flags __ro_after_init = CONFIG_SECURITY_SARA_WXPROT_DEFAULT_FLAGS; +#ifdef CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP +static const bool wxprot_emutramp = true; +#else static const bool wxprot_emutramp; +#endif #define pr_wxp(MESSAGE) do { \ char *BUF, *PATH; \ @@ -93,6 +108,9 @@ static bool are_flags_valid(u16 flags) SARA_WXP_WXORX | SARA_WXP_MMAP)))) return false; + if (unlikely(flags & SARA_WXP_EMUTRAMP && + ((flags & SARA_WXP_MPROTECT) != SARA_WXP_MPROTECT))) + return false; return true; } @@ -362,10 +380,129 @@ static int sara_file_mprotect(struct vm_area_struct *vma, return 0; } +#ifdef CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP +#define PF_PROT (1 << 0) +#define PF_USER (1 << 2) +#define PF_INSTR (1 << 4) +static int sara_pagefault_handler_x86_32(struct pt_regs *regs); +static int sara_pagefault_handler_x86_64(struct pt_regs *regs); +static int sara_pagefault_handler_x86(struct pt_regs *regs, + unsigned long error_code, + unsigned long address) +{ + int ret = 0; + + if (!sara_enabled || !wxprot_enabled || + !(error_code & PF_USER) || + !(error_code & PF_INSTR) || + !(error_code & PF_PROT) || + !(get_current_sara_wxp_flags() & SARA_WXP_EMUTRAMP)) + return 0; + + local_irq_enable(); + might_sleep(); + might_fault(); + + if (IS_ENABLED(CONFIG_X86_32) || + regs->cs == __USER32_CS || + (regs->cs & (1<<2))) { + if (!(address >> 32)) /* K8 erratum #100 */ + ret = sara_pagefault_handler_x86_32(regs); + } else + ret = sara_pagefault_handler_x86_64(regs); + + return ret; +} + +static int sara_pagefault_handler_x86_32(struct pt_regs *regs) +{ + int ret; + void __user *ip = (void __user *) regs->ip; + union trampolines_x86_32 t; + + BUILD_BUG_ON(sizeof(t.lf) > sizeof(t.g1)); + BUILD_BUG_ON(sizeof(t.g2) > sizeof(t.lf)); + + ret = copy_from_user(&t, ip, sizeof(t.g1)); + if (ret) + ret = copy_from_user(&t, ip, sizeof(t.lf)); + if (ret) + ret = copy_from_user(&t, ip, sizeof(t.g2)); + if (ret) + return 0; + + if (is_valid_gcc_trampoline_x86_32_type1(t, regs)) { + pr_debug("Trampoline: gcc1 x86_32.\n"); + emulate_gcc_trampoline_x86_32_type1(t, regs); + return 1; + } else if (is_valid_libffi_trampoline_x86_32(t)) { + pr_debug("Trampoline: libffi x86_32.\n"); + emulate_libffi_trampoline_x86_32(t, regs); + return 1; + } else if (is_valid_gcc_trampoline_x86_32_type2(t, regs)) { + pr_debug("Trampoline: gcc2 x86_32.\n"); + emulate_gcc_trampoline_x86_32_type2(t, regs); + return 1; + } + + pr_debug("Not a trampoline (x86_32).\n"); + + return 0; +} + +#ifdef CONFIG_X86_64 +static int sara_pagefault_handler_x86_64(struct pt_regs *regs) +{ + int ret; + void __user *ip = (void __user *) regs->ip; + union trampolines_x86_64 t; + + BUILD_BUG_ON(sizeof(t.g1) > sizeof(t.lf)); + BUILD_BUG_ON(sizeof(t.g2) > sizeof(t.g1)); + + ret = copy_from_user(&t, ip, sizeof(t.lf)); + if (ret) + ret = copy_from_user(&t, ip, sizeof(t.g1)); + if (ret) + ret = copy_from_user(&t, ip, sizeof(t.g2)); + if (ret) + return 0; + + if (is_valid_libffi_trampoline_x86_64(t)) { + pr_debug("Trampoline: libffi x86_64.\n"); + emulate_libffi_trampoline_x86_64(t, regs); + return 1; + } else if (is_valid_gcc_trampoline_x86_64_type1(t, regs)) { + pr_debug("Trampoline: gcc1 x86_64.\n"); + emulate_gcc_trampoline_x86_64_type1(t, regs); + return 1; + } else if (is_valid_gcc_trampoline_x86_64_type2(t, regs)) { + pr_debug("Trampoline: gcc2 x86_64.\n"); + emulate_gcc_trampoline_x86_64_type2(t, regs); + return 1; + } + + pr_debug("Not a trampoline (x86_64).\n"); + + return 0; + +} +#else /* CONFIG_X86_64 */ +static inline int sara_pagefault_handler_x86_64(struct pt_regs *regs) +{ + return 0; +} +#endif /* CONFIG_X86_64 */ + +#endif /* CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP */ + static struct security_hook_list wxprot_hooks[] __ro_after_init = { LSM_HOOK_INIT(bprm_set_creds, sara_bprm_set_creds), LSM_HOOK_INIT(check_vmflags, sara_check_vmflags), LSM_HOOK_INIT(file_mprotect, sara_file_mprotect), +#ifdef CONFIG_SECURITY_SARA_WXPROT_EMUTRAMP + LSM_HOOK_INIT(pagefault_handler_x86, sara_pagefault_handler_x86), +#endif }; struct binary_config_header {