Commit 3eb5b893 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'x86-mpx-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull x86 MPX support from Thomas Gleixner:
 "This enables support for x86 MPX.

  MPX is a new debug feature for bound checking in user space.  It
  requires kernel support to handle the bound tables and decode the
  bound violating instruction in the trap handler"

* 'x86-mpx-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  asm-generic: Remove asm-generic arch_bprm_mm_init()
  mm: Make arch_unmap()/bprm_mm_init() available to all architectures
  x86: Cleanly separate use of asm-generic/mm_hooks.h
  x86 mpx: Change return type of get_reg_offset()
  fs: Do not include mpx.h in exec.c
  x86, mpx: Add documentation on Intel MPX
  x86, mpx: Cleanup unused bound tables
  x86, mpx: On-demand kernel allocation of bounds tables
  x86, mpx: Decode MPX instruction to get bound violation information
  x86, mpx: Add MPX-specific mmap interface
  x86, mpx: Introduce VM_MPX to indicate that a VMA is MPX specific
  x86, mpx: Add MPX to disabled features
  ia64: Sync struct siginfo with general version
  mips: Sync struct siginfo with general version
  mpx: Extend siginfo structure to include bound violation information
  x86, mpx: Rename cfg_reg_u and status_reg
  x86: mpx: Give bndX registers actual names
  x86: Remove arbitrary instruction size limit in instruction decoder
parents 9e66645d 9f7789f8
This diff is collapsed.
...@@ -63,6 +63,10 @@ typedef struct siginfo { ...@@ -63,6 +63,10 @@ typedef struct siginfo {
unsigned int _flags; /* see below */ unsigned int _flags; /* see below */
unsigned long _isr; /* isr */ unsigned long _isr; /* isr */
short _addr_lsb; /* lsb of faulting address */ short _addr_lsb; /* lsb of faulting address */
struct {
void __user *_lower;
void __user *_upper;
} _addr_bnd;
} _sigfault; } _sigfault;
/* SIGPOLL */ /* SIGPOLL */
...@@ -110,9 +114,9 @@ typedef struct siginfo { ...@@ -110,9 +114,9 @@ typedef struct siginfo {
/* /*
* SIGSEGV si_codes * SIGSEGV si_codes
*/ */
#define __SEGV_PSTKOVF (__SI_FAULT|3) /* paragraph stack overflow */ #define __SEGV_PSTKOVF (__SI_FAULT|4) /* paragraph stack overflow */
#undef NSIGSEGV #undef NSIGSEGV
#define NSIGSEGV 3 #define NSIGSEGV 4
#undef NSIGTRAP #undef NSIGTRAP
#define NSIGTRAP 4 #define NSIGTRAP 4
......
...@@ -92,6 +92,10 @@ typedef struct siginfo { ...@@ -92,6 +92,10 @@ typedef struct siginfo {
int _trapno; /* TRAP # which caused the signal */ int _trapno; /* TRAP # which caused the signal */
#endif #endif
short _addr_lsb; short _addr_lsb;
struct {
void __user *_lower;
void __user *_upper;
} _addr_bnd;
} _sigfault; } _sigfault;
/* SIGPOLL, SIGXFSZ (To do ...) */ /* SIGPOLL, SIGXFSZ (To do ...) */
......
...@@ -120,4 +120,15 @@ static inline void arch_exit_mmap(struct mm_struct *mm) ...@@ -120,4 +120,15 @@ static inline void arch_exit_mmap(struct mm_struct *mm)
{ {
} }
static inline void arch_unmap(struct mm_struct *mm,
struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
}
static inline void arch_bprm_mm_init(struct mm_struct *mm,
struct vm_area_struct *vma)
{
}
#endif /* __S390_MMU_CONTEXT_H */ #endif /* __S390_MMU_CONTEXT_H */
...@@ -10,7 +10,26 @@ ...@@ -10,7 +10,26 @@
#include <asm/mmu.h> #include <asm/mmu.h>
extern void uml_setup_stubs(struct mm_struct *mm); extern void uml_setup_stubs(struct mm_struct *mm);
/*
* Needed since we do not use the asm-generic/mm_hooks.h:
*/
static inline void arch_dup_mmap(struct mm_struct *oldmm, struct mm_struct *mm)
{
uml_setup_stubs(mm);
}
extern void arch_exit_mmap(struct mm_struct *mm); extern void arch_exit_mmap(struct mm_struct *mm);
static inline void arch_unmap(struct mm_struct *mm,
struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
}
static inline void arch_bprm_mm_init(struct mm_struct *mm,
struct vm_area_struct *vma)
{
}
/*
* end asm-generic/mm_hooks.h functions
*/
#define deactivate_mm(tsk,mm) do { } while (0) #define deactivate_mm(tsk,mm) do { } while (0)
...@@ -41,11 +60,6 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, ...@@ -41,11 +60,6 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,
} }
} }
static inline void arch_dup_mmap(struct mm_struct *oldmm, struct mm_struct *mm)
{
uml_setup_stubs(mm);
}
static inline void enter_lazy_tlb(struct mm_struct *mm, static inline void enter_lazy_tlb(struct mm_struct *mm,
struct task_struct *tsk) struct task_struct *tsk)
{ {
......
...@@ -86,4 +86,15 @@ static inline void arch_dup_mmap(struct mm_struct *oldmm, ...@@ -86,4 +86,15 @@ static inline void arch_dup_mmap(struct mm_struct *oldmm,
{ {
} }
static inline void arch_unmap(struct mm_struct *mm,
struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
}
static inline void arch_bprm_mm_init(struct mm_struct *mm,
struct vm_area_struct *vma)
{
}
#endif #endif
...@@ -248,6 +248,10 @@ config HAVE_INTEL_TXT ...@@ -248,6 +248,10 @@ config HAVE_INTEL_TXT
def_bool y def_bool y
depends on INTEL_IOMMU && ACPI depends on INTEL_IOMMU && ACPI
config X86_INTEL_MPX
def_bool y
depends on CPU_SUP_INTEL
config X86_32_SMP config X86_32_SMP
def_bool y def_bool y
depends on X86_32 && SMP depends on X86_32 && SMP
......
...@@ -10,6 +10,12 @@ ...@@ -10,6 +10,12 @@
* cpu_feature_enabled(). * cpu_feature_enabled().
*/ */
#ifdef CONFIG_X86_INTEL_MPX
# define DISABLE_MPX 0
#else
# define DISABLE_MPX (1<<(X86_FEATURE_MPX & 31))
#endif
#ifdef CONFIG_X86_64 #ifdef CONFIG_X86_64
# define DISABLE_VME (1<<(X86_FEATURE_VME & 31)) # define DISABLE_VME (1<<(X86_FEATURE_VME & 31))
# define DISABLE_K6_MTRR (1<<(X86_FEATURE_K6_MTRR & 31)) # define DISABLE_K6_MTRR (1<<(X86_FEATURE_K6_MTRR & 31))
...@@ -34,6 +40,6 @@ ...@@ -34,6 +40,6 @@
#define DISABLED_MASK6 0 #define DISABLED_MASK6 0
#define DISABLED_MASK7 0 #define DISABLED_MASK7 0
#define DISABLED_MASK8 0 #define DISABLED_MASK8 0
#define DISABLED_MASK9 0 #define DISABLED_MASK9 (DISABLE_MPX)
#endif /* _ASM_X86_DISABLED_FEATURES_H */ #endif /* _ASM_X86_DISABLED_FEATURES_H */
...@@ -65,6 +65,7 @@ struct insn { ...@@ -65,6 +65,7 @@ struct insn {
unsigned char x86_64; unsigned char x86_64;
const insn_byte_t *kaddr; /* kernel address of insn to analyze */ const insn_byte_t *kaddr; /* kernel address of insn to analyze */
const insn_byte_t *end_kaddr; /* kernel address of last insn in buffer */
const insn_byte_t *next_byte; const insn_byte_t *next_byte;
}; };
...@@ -96,7 +97,7 @@ struct insn { ...@@ -96,7 +97,7 @@ struct insn {
#define X86_VEX_P(vex) ((vex) & 0x03) /* VEX3 Byte2, VEX2 Byte1 */ #define X86_VEX_P(vex) ((vex) & 0x03) /* VEX3 Byte2, VEX2 Byte1 */
#define X86_VEX_M_MAX 0x1f /* VEX3.M Maximum value */ #define X86_VEX_M_MAX 0x1f /* VEX3.M Maximum value */
extern void insn_init(struct insn *insn, const void *kaddr, int x86_64); extern void insn_init(struct insn *insn, const void *kaddr, int buf_len, int x86_64);
extern void insn_get_prefixes(struct insn *insn); extern void insn_get_prefixes(struct insn *insn);
extern void insn_get_opcode(struct insn *insn); extern void insn_get_opcode(struct insn *insn);
extern void insn_get_modrm(struct insn *insn); extern void insn_get_modrm(struct insn *insn);
...@@ -115,12 +116,13 @@ static inline void insn_get_attribute(struct insn *insn) ...@@ -115,12 +116,13 @@ static inline void insn_get_attribute(struct insn *insn)
extern int insn_rip_relative(struct insn *insn); extern int insn_rip_relative(struct insn *insn);
/* Init insn for kernel text */ /* Init insn for kernel text */
static inline void kernel_insn_init(struct insn *insn, const void *kaddr) static inline void kernel_insn_init(struct insn *insn,
const void *kaddr, int buf_len)
{ {
#ifdef CONFIG_X86_64 #ifdef CONFIG_X86_64
insn_init(insn, kaddr, 1); insn_init(insn, kaddr, buf_len, 1);
#else /* CONFIG_X86_32 */ #else /* CONFIG_X86_32 */
insn_init(insn, kaddr, 0); insn_init(insn, kaddr, buf_len, 0);
#endif #endif
} }
......
...@@ -10,9 +10,8 @@ ...@@ -10,9 +10,8 @@
#include <asm/pgalloc.h> #include <asm/pgalloc.h>
#include <asm/tlbflush.h> #include <asm/tlbflush.h>
#include <asm/paravirt.h> #include <asm/paravirt.h>
#include <asm/mpx.h>
#ifndef CONFIG_PARAVIRT #ifndef CONFIG_PARAVIRT
#include <asm-generic/mm_hooks.h>
static inline void paravirt_activate_mm(struct mm_struct *prev, static inline void paravirt_activate_mm(struct mm_struct *prev,
struct mm_struct *next) struct mm_struct *next)
{ {
...@@ -102,4 +101,27 @@ do { \ ...@@ -102,4 +101,27 @@ do { \
} while (0) } while (0)
#endif #endif
static inline void arch_dup_mmap(struct mm_struct *oldmm,
struct mm_struct *mm)
{
paravirt_arch_dup_mmap(oldmm, mm);
}
static inline void arch_exit_mmap(struct mm_struct *mm)
{
paravirt_arch_exit_mmap(mm);
}
static inline void arch_bprm_mm_init(struct mm_struct *mm,
struct vm_area_struct *vma)
{
mpx_mm_init(mm);
}
static inline void arch_unmap(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
mpx_notify_unmap(mm, vma, start, end);
}
#endif /* _ASM_X86_MMU_CONTEXT_H */ #endif /* _ASM_X86_MMU_CONTEXT_H */
#ifndef _ASM_X86_MPX_H
#define _ASM_X86_MPX_H
#include <linux/types.h>
#include <asm/ptrace.h>
#include <asm/insn.h>
/*
* NULL is theoretically a valid place to put the bounds
* directory, so point this at an invalid address.
*/
#define MPX_INVALID_BOUNDS_DIR ((void __user *)-1)
#define MPX_BNDCFG_ENABLE_FLAG 0x1
#define MPX_BD_ENTRY_VALID_FLAG 0x1
#ifdef CONFIG_X86_64
/* upper 28 bits [47:20] of the virtual address in 64-bit used to
* index into bounds directory (BD).
*/
#define MPX_BD_ENTRY_OFFSET 28
#define MPX_BD_ENTRY_SHIFT 3
/* bits [19:3] of the virtual address in 64-bit used to index into
* bounds table (BT).
*/
#define MPX_BT_ENTRY_OFFSET 17
#define MPX_BT_ENTRY_SHIFT 5
#define MPX_IGN_BITS 3
#define MPX_BD_ENTRY_TAIL 3
#else
#define MPX_BD_ENTRY_OFFSET 20
#define MPX_BD_ENTRY_SHIFT 2
#define MPX_BT_ENTRY_OFFSET 10
#define MPX_BT_ENTRY_SHIFT 4
#define MPX_IGN_BITS 2
#define MPX_BD_ENTRY_TAIL 2
#endif
#define MPX_BD_SIZE_BYTES (1UL<<(MPX_BD_ENTRY_OFFSET+MPX_BD_ENTRY_SHIFT))
#define MPX_BT_SIZE_BYTES (1UL<<(MPX_BT_ENTRY_OFFSET+MPX_BT_ENTRY_SHIFT))
#define MPX_BNDSTA_TAIL 2
#define MPX_BNDCFG_TAIL 12
#define MPX_BNDSTA_ADDR_MASK (~((1UL<<MPX_BNDSTA_TAIL)-1))
#define MPX_BNDCFG_ADDR_MASK (~((1UL<<MPX_BNDCFG_TAIL)-1))
#define MPX_BT_ADDR_MASK (~((1UL<<MPX_BD_ENTRY_TAIL)-1))
#define MPX_BNDCFG_ADDR_MASK (~((1UL<<MPX_BNDCFG_TAIL)-1))
#define MPX_BNDSTA_ERROR_CODE 0x3
#define MPX_BD_ENTRY_MASK ((1<<MPX_BD_ENTRY_OFFSET)-1)
#define MPX_BT_ENTRY_MASK ((1<<MPX_BT_ENTRY_OFFSET)-1)
#define MPX_GET_BD_ENTRY_OFFSET(addr) ((((addr)>>(MPX_BT_ENTRY_OFFSET+ \
MPX_IGN_BITS)) & MPX_BD_ENTRY_MASK) << MPX_BD_ENTRY_SHIFT)
#define MPX_GET_BT_ENTRY_OFFSET(addr) ((((addr)>>MPX_IGN_BITS) & \
MPX_BT_ENTRY_MASK) << MPX_BT_ENTRY_SHIFT)
#ifdef CONFIG_X86_INTEL_MPX
siginfo_t *mpx_generate_siginfo(struct pt_regs *regs,
struct xsave_struct *xsave_buf);
int mpx_handle_bd_fault(struct xsave_struct *xsave_buf);
static inline int kernel_managing_mpx_tables(struct mm_struct *mm)
{
return (mm->bd_addr != MPX_INVALID_BOUNDS_DIR);
}
static inline void mpx_mm_init(struct mm_struct *mm)
{
/*
* NULL is theoretically a valid place to put the bounds
* directory, so point this at an invalid address.
*/
mm->bd_addr = MPX_INVALID_BOUNDS_DIR;
}
void mpx_notify_unmap(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long start, unsigned long end);
#else
static inline siginfo_t *mpx_generate_siginfo(struct pt_regs *regs,
struct xsave_struct *xsave_buf)
{
return NULL;
}
static inline int mpx_handle_bd_fault(struct xsave_struct *xsave_buf)
{
return -EINVAL;
}
static inline int kernel_managing_mpx_tables(struct mm_struct *mm)
{
return 0;
}
static inline void mpx_mm_init(struct mm_struct *mm)
{
}
static inline void mpx_notify_unmap(struct mm_struct *mm,
struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
}
#endif /* CONFIG_X86_INTEL_MPX */
#endif /* _ASM_X86_MPX_H */
...@@ -330,13 +330,13 @@ static inline void paravirt_activate_mm(struct mm_struct *prev, ...@@ -330,13 +330,13 @@ static inline void paravirt_activate_mm(struct mm_struct *prev,
PVOP_VCALL2(pv_mmu_ops.activate_mm, prev, next); PVOP_VCALL2(pv_mmu_ops.activate_mm, prev, next);
} }
static inline void arch_dup_mmap(struct mm_struct *oldmm, static inline void paravirt_arch_dup_mmap(struct mm_struct *oldmm,
struct mm_struct *mm) struct mm_struct *mm)
{ {
PVOP_VCALL2(pv_mmu_ops.dup_mmap, oldmm, mm); PVOP_VCALL2(pv_mmu_ops.dup_mmap, oldmm, mm);
} }
static inline void arch_exit_mmap(struct mm_struct *mm) static inline void paravirt_arch_exit_mmap(struct mm_struct *mm)
{ {
PVOP_VCALL1(pv_mmu_ops.exit_mmap, mm); PVOP_VCALL1(pv_mmu_ops.exit_mmap, mm);
} }
...@@ -986,5 +986,15 @@ extern void default_banner(void); ...@@ -986,5 +986,15 @@ extern void default_banner(void);
#endif /* __ASSEMBLY__ */ #endif /* __ASSEMBLY__ */
#else /* CONFIG_PARAVIRT */ #else /* CONFIG_PARAVIRT */
# define default_banner x86_init_noop # define default_banner x86_init_noop
#ifndef __ASSEMBLY__
static inline void paravirt_arch_dup_mmap(struct mm_struct *oldmm,
struct mm_struct *mm)
{
}
static inline void paravirt_arch_exit_mmap(struct mm_struct *mm)
{
}
#endif /* __ASSEMBLY__ */
#endif /* !CONFIG_PARAVIRT */ #endif /* !CONFIG_PARAVIRT */
#endif /* _ASM_X86_PARAVIRT_H */ #endif /* _ASM_X86_PARAVIRT_H */
...@@ -374,13 +374,14 @@ struct lwp_struct { ...@@ -374,13 +374,14 @@ struct lwp_struct {
u8 reserved[128]; u8 reserved[128];
}; };
struct bndregs_struct { struct bndreg {
u64 bndregs[8]; u64 lower_bound;
u64 upper_bound;
} __packed; } __packed;
struct bndcsr_struct { struct bndcsr {
u64 cfg_reg_u; u64 bndcfgu;
u64 status_reg; u64 bndstatus;
} __packed; } __packed;
struct xsave_hdr_struct { struct xsave_hdr_struct {
...@@ -394,8 +395,8 @@ struct xsave_struct { ...@@ -394,8 +395,8 @@ struct xsave_struct {
struct xsave_hdr_struct xsave_hdr; struct xsave_hdr_struct xsave_hdr;
struct ymmh_struct ymmh; struct ymmh_struct ymmh;
struct lwp_struct lwp; struct lwp_struct lwp;
struct bndregs_struct bndregs; struct bndreg bndreg[4];
struct bndcsr_struct bndcsr; struct bndcsr bndcsr;
/* new processor state extensions will go here */ /* new processor state extensions will go here */
} __attribute__ ((packed, aligned (64))); } __attribute__ ((packed, aligned (64)));
...@@ -953,6 +954,24 @@ extern void start_thread(struct pt_regs *regs, unsigned long new_ip, ...@@ -953,6 +954,24 @@ extern void start_thread(struct pt_regs *regs, unsigned long new_ip,
extern int get_tsc_mode(unsigned long adr); extern int get_tsc_mode(unsigned long adr);
extern int set_tsc_mode(unsigned int val); extern int set_tsc_mode(unsigned int val);
/* Register/unregister a process' MPX related resource */
#define MPX_ENABLE_MANAGEMENT(tsk) mpx_enable_management((tsk))
#define MPX_DISABLE_MANAGEMENT(tsk) mpx_disable_management((tsk))
#ifdef CONFIG_X86_INTEL_MPX
extern int mpx_enable_management(struct task_struct *tsk);
extern int mpx_disable_management(struct task_struct *tsk);
#else
static inline int mpx_enable_management(struct task_struct *tsk)
{
return -EINVAL;
}
static inline int mpx_disable_management(struct task_struct *tsk)
{
return -EINVAL;
}
#endif /* CONFIG_X86_INTEL_MPX */
extern u16 amd_get_nb_id(int cpu); extern u16 amd_get_nb_id(int cpu);
static inline uint32_t hypervisor_cpuid_base(const char *sig, uint32_t leaves) static inline uint32_t hypervisor_cpuid_base(const char *sig, uint32_t leaves)
......
...@@ -724,6 +724,7 @@ static int intel_pmu_pebs_fixup_ip(struct pt_regs *regs) ...@@ -724,6 +724,7 @@ static int intel_pmu_pebs_fixup_ip(struct pt_regs *regs)
unsigned long ip = regs->ip; unsigned long ip = regs->ip;
int is_64bit = 0; int is_64bit = 0;
void *kaddr; void *kaddr;
int size;
/* /*
* We don't need to fixup if the PEBS assist is fault like * We don't need to fixup if the PEBS assist is fault like
...@@ -758,11 +759,12 @@ static int intel_pmu_pebs_fixup_ip(struct pt_regs *regs) ...@@ -758,11 +759,12 @@ static int intel_pmu_pebs_fixup_ip(struct pt_regs *regs)
return 1; return 1;
} }
size = ip - to;
if (!kernel_ip(ip)) { if (!kernel_ip(ip)) {
int size, bytes; int bytes;
u8 *buf = this_cpu_read(insn_buffer); u8 *buf = this_cpu_read(insn_buffer);
size = ip - to; /* Must fit our buffer, see above */ /* 'size' must fit our buffer, see above */
bytes = copy_from_user_nmi(buf, (void __user *)to, size); bytes = copy_from_user_nmi(buf, (void __user *)to, size);
if (bytes != 0) if (bytes != 0)
return 0; return 0;
...@@ -780,11 +782,20 @@ static int intel_pmu_pebs_fixup_ip(struct pt_regs *regs) ...@@ -780,11 +782,20 @@ static int intel_pmu_pebs_fixup_ip(struct pt_regs *regs)
#ifdef CONFIG_X86_64 #ifdef CONFIG_X86_64
is_64bit = kernel_ip(to) || !test_thread_flag(TIF_IA32); is_64bit = kernel_ip(to) || !test_thread_flag(TIF_IA32);
#endif #endif
insn_init(&insn, kaddr, is_64bit); insn_init(&insn, kaddr, size, is_64bit);
insn_get_length(&insn); insn_get_length(&insn);
/*
* Make sure there was not a problem decoding the
* instruction and getting the length. This is
* doubly important because we have an infinite
* loop if insn.length=0.
*/
if (!insn.length)
break;
to += insn.length; to += insn.length;
kaddr += insn.length; kaddr += insn.length;
size -= insn.length;
} while (to < ip); } while (to < ip);
if (to == ip) { if (to == ip) {
......
...@@ -465,7 +465,7 @@ static int branch_type(unsigned long from, unsigned long to, int abort) ...@@ -465,7 +465,7 @@ static int branch_type(unsigned long from, unsigned long to, int abort)
{ {
struct insn insn; struct insn insn;
void *addr; void *addr;
int bytes, size = MAX_INSN_SIZE; int bytes_read, bytes_left;
int ret = X86_BR_NONE; int ret = X86_BR_NONE;
int ext, to_plm, from_plm; int ext, to_plm, from_plm;
u8 buf[MAX_INSN_SIZE]; u8 buf[MAX_INSN_SIZE];
...@@ -493,8 +493,10 @@ static int branch_type(unsigned long from, unsigned long to, int abort) ...@@ -493,8 +493,10 @@ static int branch_type(unsigned long from, unsigned long to, int abort)
return X86_BR_NONE; return X86_BR_NONE;
/* may fail if text not present */ /* may fail if text not present */
bytes = copy_from_user_nmi(buf, (void __user *)from, size); bytes_left = copy_from_user_nmi(buf, (void __user *)from,
if (bytes != 0) MAX_INSN_SIZE);
bytes_read = MAX_INSN_SIZE - bytes_left;
if (!bytes_read)
return X86_BR_NONE; return X86_BR_NONE;
addr = buf; addr = buf;
...@@ -505,10 +507,19 @@ static int branch_type(unsigned long from, unsigned long to, int abort) ...@@ -505,10 +507,19 @@ static int branch_type(unsigned long from, unsigned long to, int abort)
* Ensure we don't blindy read any address by validating it is * Ensure we don't blindy read any address by validating it is
* a known text address. * a known text address.
*/ */
if (kernel_text_address(from)) if (kernel_text_address(from)) {
addr = (void *)from; addr = (void *)from;
else /*
* Assume we can get the maximum possible size
* when grabbing kernel data. This is not
* _strictly_ true since we could possibly be
* executing up next to a memory hole, but
* it is very unlikely to be a problem.
*/
bytes_read = MAX_INSN_SIZE;
} else {
return X86_BR_NONE; return X86_BR_NONE;
}
} }
/* /*
...@@ -518,8 +529,10 @@ static int branch_type(unsigned long from, unsigned long to, int abort) ...@@ -518,8 +529,10 @@ static int branch_type(unsigned long from, unsigned long to, int abort)
#ifdef CONFIG_X86_64 #ifdef CONFIG_X86_64
is64 = kernel_ip((unsigned long)addr) || !test_thread_flag(TIF_IA32); is64 = kernel_ip((unsigned long)addr) || !test_thread_flag(TIF_IA32);
#endif #endif
insn_init(&insn, addr, is64); insn_init(&insn, addr, bytes_read, is64);
insn_get_opcode(&insn); insn_get_opcode(&insn);
if (!insn.opcode.got)
return X86_BR_ABORT;
switch (insn.opcode.bytes[0]) { switch (insn.opcode.bytes[0]) {
case 0xf: case 0xf:
......
...@@ -285,7 +285,7 @@ static int can_probe(unsigned long paddr) ...@@ -285,7 +285,7 @@ static int can_probe(unsigned long paddr)
* normally used, we just go through if there is no kprobe. * normally used, we just go through if there is no kprobe.
*/ */
__addr = recover_probed_instruction(buf, addr); __addr = recover_probed_instruction(buf, addr);
kernel_insn_init(&insn, (void *)__addr); kernel_insn_init(&insn, (void *)__addr, MAX_INSN_SIZE);
insn_get_length(&insn); insn_get_length(&insn);
/* /*
...@@ -330,8 +330,10 @@ int __copy_instruction(u8 *dest, u8 *src) ...@@ -330,8 +330,10 @@ int __copy_instruction(u8 *dest, u8 *src)
{ {
struct insn insn; struct insn insn;
kprobe_opcode_t buf[MAX_INSN_SIZE]; kprobe_opcode_t buf[MAX_INSN_SIZE];
unsigned long recovered_insn =
recover_probed_instruction(buf, (unsigned long)src);
kernel_insn_init(&insn, (void *)recover_probed_instruction(buf, (unsigned long)src)); kernel_insn_init(&insn, (void *)recovered_insn, MAX_INSN_SIZE);
insn_get_length(&insn); insn_get_length(&insn);
/* Another subsystem puts a breakpoint, failed to recover */ /* Another subsystem puts a breakpoint, failed to recover */
if (insn.opcode.bytes[0] == BREAKPOINT_INSTRUCTION) if (insn.opcode.bytes[0] == BREAKPOINT_INSTRUCTION)
...@@ -342,7 +344,7 @@ int __copy_instruction(u8 *dest, u8 *src) ...@@ -342,7 +344,7 @@ int __copy_instruction(u8 *dest, u8 *src)
if (insn_rip_relative(&insn)) { if (insn_rip_relative(&insn)) {
s64 newdisp; s64 newdisp;
u8 *disp; u8 *disp;
kernel_insn_init(&insn, dest); kernel_insn_init(&insn, dest, insn.length);
insn_get_displacement(&insn); insn_get_displacement(&insn);
/* /*
* The copied instruction uses the %rip-relative addressing * The copied instruction uses the %rip-relative addressing
......
...@@ -251,13 +251,15 @@ static int can_optimize(unsigned long paddr) ...@@ -251,13 +251,15 @@ static int can_optimize(unsigned long paddr)
/* Decode instructions */ /* Decode instructions */
addr = paddr - offset; addr = paddr - offset;
while (addr < paddr - offset + size) { /* Decode until function end */ while (addr < paddr - offset + size) { /* Decode until function end */
unsigned long recovered_insn;
if (search_exception_tables(addr)) if (search_exception_tables(addr))
/* /*
* Since some fixup code will jumps into this function, * Since some fixup code will jumps into this function,
* we can't optimize kprobe in this function. * we can't optimize kprobe in this function.
*/ */
return 0; return 0;
kernel_insn_init(&insn, (void *)recover_probed_instruction(buf, addr)); recovered_insn = recover_probed_instruction(buf, addr);
kernel_insn_init(&insn, (void *)recovered_insn, MAX_INSN_SIZE);
insn_get_length(&insn); insn_get_length(&insn);
/* Another subsystem puts a breakpoint */ /* Another subsystem puts a breakpoint */
if (insn.opcode.bytes[0] == BREAKPOINT_INSTRUCTION) if (insn.opcode.bytes[0] == BREAKPOINT_INSTRUCTION)
......
...@@ -960,6 +960,8 @@ void __init setup_arch(char **cmdline_p) ...@@ -960,6 +960,8 @@ void __init setup_arch(char **cmdline_p)
init_mm.end_data = (unsigned long) _edata; init_mm.end_data = (unsigned long) _edata;
init_mm.brk = _brk_end; init_mm.brk = _brk_end;
mpx_mm_init(&init_mm);
code_resource.start = __pa_symbol(_text); code_resource.start = __pa_symbol(_text);
code_resource.end = __pa_symbol(_etext)-1; code_resource.end = __pa_symbol(_etext)-1;
data_resource.start = __pa_symbol(_etext); data_resource.start = __pa_symbol(_etext);
......
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
#include <asm/fixmap.h> #include <asm/fixmap.h>
#include <asm/mach_traps.h> #include <asm/mach_traps.h>
#include <asm/alternative.h> #include <asm/alternative.h>
#include <asm/mpx.h>
#ifdef CONFIG_X86_64 #ifdef CONFIG_X86_64
#include <asm/x86_init.h> #include <asm/x86_init.h>
...@@ -228,7 +229,6 @@ dotraplinkage void do_##name(struct pt_regs *regs, long error_code) \ ...@@ -228,7 +229,6 @@ dotraplinkage void do_##name(struct pt_regs *regs, long error_code) \
DO_ERROR(X86_TRAP_DE, SIGFPE, "divide error", divide_error) DO_ERROR(X86_TRAP_DE, SIGFPE, "divide error", divide_error)
DO_ERROR(X86_TRAP_OF, SIGSEGV, "overflow", overflow) DO_ERROR(X86_TRAP_OF, SIGSEGV, "overflow", overflow)
DO_ERROR(X86_TRAP_BR, SIGSEGV, "bounds", bounds)
DO_ERROR(X86_TRAP_UD, SIGILL, "invalid opcode", invalid_op) DO_ERROR(X86_TRAP_UD, SIGILL, "invalid opcode", invalid_op)
DO_ERROR(X86_TRAP_OLD_MF, SIGFPE, "coprocessor segment overrun",coprocessor_segment_overrun) DO_ERROR(X86_TRAP_OLD_MF, SIGFPE, "coprocessor segment overrun",coprocessor_segment_overrun)
DO_ERROR(X86_TRAP_TS, SIGSEGV, "invalid TSS", invalid_TSS) DO_ERROR(X86_TRAP_TS, SIGSEGV, "invalid TSS", invalid_TSS)
...@@ -286,6 +286,89 @@ dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code) ...@@ -286,6 +286,89 @@ dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code)
} }
#endif #endif
dotraplinkage void do_bounds(struct pt_regs *regs, long error_code)
{
struct task_struct *tsk = current;
struct xsave_struct *xsave_buf;
enum ctx_state prev_state;
struct bndcsr *bndcsr;
siginfo_t *info;
prev_state = exception_enter();
if (notify_die(DIE_TRAP, "bounds", regs, error_code,
X86_TRAP_BR, SIGSEGV) == NOTIFY_STOP)
goto exit;
conditional_sti(regs);
if (!user_mode(regs))
die("bounds", regs, error_code);
if (!cpu_feature_enabled(X86_FEATURE_MPX)) {
/* The exception is not from Intel MPX */
goto exit_trap;
}
/*
* We need to look at BNDSTATUS to resolve this exception.
* It is not directly accessible, though, so we need to
* do an xsave and then pull it out of the xsave buffer.
*/
fpu_save_init(&tsk->thread.fpu);
xsave_buf = &(tsk->thread.fpu.state->xsave);
bndcsr = get_xsave_addr(xsave_buf, XSTATE_BNDCSR);
if (!bndcsr)
goto exit_trap;
/*
* The error code field of the BNDSTATUS register communicates status
* information of a bound range exception #BR or operation involving
* bound directory.
*/
switch (bndcsr->bndstatus & MPX_BNDSTA_ERROR_CODE) {
case 2: /* Bound directory has invalid entry. */
if (mpx_handle_bd_fault(xsave_buf))
goto exit_trap;
break; /* Success, it was handled */
case 1: /* Bound violation. */
info = mpx_generate_siginfo(regs, xsave_buf);
if (PTR_ERR(info)) {
/*
* We failed to decode the MPX instruction. Act as if
* the exception was not caused by MPX.
*/
goto exit_trap;
}
/*
* Success, we decoded the instruction and retrieved
* an 'info' containing the address being accessed
* which caused the exception. This information
* allows and application to possibly handle the
* #BR exception itself.
*/
do_trap(X86_TRAP_BR, SIGSEGV, "bounds", regs, error_code, info);
kfree(info);
break;
case 0: /* No exception caused by Intel MPX operations. */
goto exit_trap;
default:
die("bounds", regs, error_code);
}
exit:
exception_exit(prev_state);
return;
exit_trap:
/*
* This path out is for all the cases where we could not
* handle the exception in some way (like allocating a
* table or telling userspace about it. We will also end
* up here if the kernel has MPX turned off at compile
* time..
*/
do_trap(X86_TRAP_BR, SIGSEGV, "bounds", regs, error_code, NULL);
exception_exit(prev_state);
}
dotraplinkage void dotraplinkage void
do_general_protection(struct pt_regs *regs, long error_code) do_general_protection(struct pt_regs *regs, long error_code)
{ {
......
...@@ -219,7 +219,7 @@ static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool ...@@ -219,7 +219,7 @@ static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool
{ {
u32 volatile *good_insns; u32 volatile *good_insns;
insn_init(insn, auprobe->insn, x86_64); insn_init(insn, auprobe->insn, sizeof(auprobe->insn), x86_64);
/* has the side-effect of processing the entire instruction */ /* has the side-effect of processing the entire instruction */
insn_get_length(insn); insn_get_length(insn);
if (WARN_ON_ONCE(!insn_complete(insn))) if (WARN_ON_ONCE(!insn_complete(insn)))
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
/* Verify next sizeof(t) bytes can be on the same instruction */ /* Verify next sizeof(t) bytes can be on the same instruction */
#define validate_next(t, insn, n) \ #define validate_next(t, insn, n) \
((insn)->next_byte + sizeof(t) + n - (insn)->kaddr <= MAX_INSN_SIZE) ((insn)->next_byte + sizeof(t) + n < (insn)->end_kaddr)
#define __get_next(t, insn) \ #define __get_next(t, insn) \
({ t r = *(t*)insn->next_byte; insn->next_byte += sizeof(t); r; }) ({ t r = *(t*)insn->next_byte; insn->next_byte += sizeof(t); r; })
...@@ -50,10 +50,11 @@ ...@@ -50,10 +50,11 @@
* @kaddr: address (in kernel memory) of instruction (or copy thereof) * @kaddr: address (in kernel memory) of instruction (or copy thereof)
* @x86_64: !0 for 64-bit kernel or 64-bit app * @x86_64: !0 for 64-bit kernel or 64-bit app
*/ */
void insn_init(struct insn *insn, const void *kaddr, int x86_64) void insn_init(struct insn *insn, const void *kaddr, int buf_len, int x86_64)
{ {
memset(insn, 0, sizeof(*insn)); memset(insn, 0, sizeof(*insn));
insn->kaddr = kaddr; insn->kaddr = kaddr;
insn->end_kaddr = kaddr + buf_len;
insn->next_byte = kaddr; insn->next_byte = kaddr;
insn->x86_64 = x86_64 ? 1 : 0; insn->x86_64 = x86_64 ? 1 : 0;
insn->opnd_bytes = 4; insn->opnd_bytes = 4;
......
...@@ -30,3 +30,5 @@ obj-$(CONFIG_ACPI_NUMA) += srat.o ...@@ -30,3 +30,5 @@ obj-$(CONFIG_ACPI_NUMA) += srat.o
obj-$(CONFIG_NUMA_EMU) += numa_emulation.o obj-$(CONFIG_NUMA_EMU) += numa_emulation.o
obj-$(CONFIG_MEMTEST) += memtest.o obj-$(CONFIG_MEMTEST) += memtest.o
obj-$(CONFIG_X86_INTEL_MPX) += mpx.o
This diff is collapsed.
...@@ -254,7 +254,7 @@ int main(int argc, char **argv) ...@@ -254,7 +254,7 @@ int main(int argc, char **argv)
continue; continue;
/* Decode an instruction */ /* Decode an instruction */
insn_init(&insn, insn_buf, x86_64); insn_init(&insn, insn_buf, sizeof(insn_buf), x86_64);
insn_get_length(&insn); insn_get_length(&insn);
if (insn.next_byte <= insn.kaddr || if (insn.next_byte <= insn.kaddr ||
......
...@@ -149,7 +149,7 @@ int main(int argc, char **argv) ...@@ -149,7 +149,7 @@ int main(int argc, char **argv)
break; break;
} }
/* Decode an instruction */ /* Decode an instruction */
insn_init(&insn, insn_buf, x86_64); insn_init(&insn, insn_buf, sizeof(insn_buf), x86_64);
insn_get_length(&insn); insn_get_length(&insn);
if (insn.length != nb) { if (insn.length != nb) {
warnings++; warnings++;
......
...@@ -277,6 +277,7 @@ static int __bprm_mm_init(struct linux_binprm *bprm) ...@@ -277,6 +277,7 @@ static int __bprm_mm_init(struct linux_binprm *bprm)
goto err; goto err;
mm->stack_vm = mm->total_vm = 1; mm->stack_vm = mm->total_vm = 1;
arch_bprm_mm_init(mm, vma);
up_write(&mm->mmap_sem); up_write(&mm->mmap_sem);
bprm->p = vma->vm_end - sizeof(void *); bprm->p = vma->vm_end - sizeof(void *);
return 0; return 0;
......
...@@ -552,6 +552,9 @@ static void show_smap_vma_flags(struct seq_file *m, struct vm_area_struct *vma) ...@@ -552,6 +552,9 @@ static void show_smap_vma_flags(struct seq_file *m, struct vm_area_struct *vma)
[ilog2(VM_GROWSDOWN)] = "gd", [ilog2(VM_GROWSDOWN)] = "gd",
[ilog2(VM_PFNMAP)] = "pf", [ilog2(VM_PFNMAP)] = "pf",
[ilog2(VM_DENYWRITE)] = "dw", [ilog2(VM_DENYWRITE)] = "dw",
#ifdef CONFIG_X86_INTEL_MPX
[ilog2(VM_MPX)] = "mp",
#endif
[ilog2(VM_LOCKED)] = "lo", [ilog2(VM_LOCKED)] = "lo",
[ilog2(VM_IO)] = "io", [ilog2(VM_IO)] = "io",
[ilog2(VM_SEQ_READ)] = "sr", [ilog2(VM_SEQ_READ)] = "sr",
......
/* /*
* Define generic no-op hooks for arch_dup_mmap and arch_exit_mmap, to * Define generic no-op hooks for arch_dup_mmap, arch_exit_mmap
* be included in asm-FOO/mmu_context.h for any arch FOO which doesn't * and arch_unmap to be included in asm-FOO/mmu_context.h for any
* need to hook these. * arch FOO which doesn't need to hook these.
*/ */
#ifndef _ASM_GENERIC_MM_HOOKS_H #ifndef _ASM_GENERIC_MM_HOOKS_H
#define _ASM_GENERIC_MM_HOOKS_H #define _ASM_GENERIC_MM_HOOKS_H
...@@ -15,4 +15,15 @@ static inline void arch_exit_mmap(struct mm_struct *mm) ...@@ -15,4 +15,15 @@ static inline void arch_exit_mmap(struct mm_struct *mm)
{ {
} }
static inline void arch_unmap(struct mm_struct *mm,
struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
}
static inline void arch_bprm_mm_init(struct mm_struct *mm,
struct vm_area_struct *vma)
{
}
#endif /* _ASM_GENERIC_MM_HOOKS_H */ #endif /* _ASM_GENERIC_MM_HOOKS_H */
...@@ -128,6 +128,7 @@ extern unsigned int kobjsize(const void *objp); ...@@ -128,6 +128,7 @@ extern unsigned int kobjsize(const void *objp);
#define VM_HUGETLB 0x00400000 /* Huge TLB Page VM */ #define VM_HUGETLB 0x00400000 /* Huge TLB Page VM */
#define VM_NONLINEAR 0x00800000 /* Is non-linear (remap_file_pages) */ #define VM_NONLINEAR 0x00800000 /* Is non-linear (remap_file_pages) */
#define VM_ARCH_1 0x01000000 /* Architecture-specific flag */ #define VM_ARCH_1 0x01000000 /* Architecture-specific flag */
#define VM_ARCH_2 0x02000000
#define VM_DONTDUMP 0x04000000 /* Do not include in the core dump */ #define VM_DONTDUMP 0x04000000 /* Do not include in the core dump */
#ifdef CONFIG_MEM_SOFT_DIRTY #ifdef CONFIG_MEM_SOFT_DIRTY
...@@ -155,6 +156,11 @@ extern unsigned int kobjsize(const void *objp); ...@@ -155,6 +156,11 @@ extern unsigned int kobjsize(const void *objp);
# define VM_MAPPED_COPY VM_ARCH_1 /* T if mapped copy of data (nommu mmap) */ # define VM_MAPPED_COPY VM_ARCH_1 /* T if mapped copy of data (nommu mmap) */
#endif #endif
#if defined(CONFIG_X86)
/* MPX specific bounds table or bounds directory */
# define VM_MPX VM_ARCH_2
#endif
#ifndef VM_GROWSUP #ifndef VM_GROWSUP
# define VM_GROWSUP VM_NONE # define VM_GROWSUP VM_NONE
#endif #endif
......
...@@ -454,6 +454,10 @@ struct mm_struct { ...@@ -454,6 +454,10 @@ struct mm_struct {
bool tlb_flush_pending; bool tlb_flush_pending;
#endif #endif
struct uprobes_state uprobes_state; struct uprobes_state uprobes_state;
#ifdef CONFIG_X86_INTEL_MPX
/* address of the bounds directory */
void __user *bd_addr;
#endif
}; };
static inline void mm_init_cpumask(struct mm_struct *mm) static inline void mm_init_cpumask(struct mm_struct *mm)
......
...@@ -91,6 +91,10 @@ typedef struct siginfo { ...@@ -91,6 +91,10 @@ typedef struct siginfo {
int _trapno; /* TRAP # which caused the signal */ int _trapno; /* TRAP # which caused the signal */
#endif #endif
short _addr_lsb; /* LSB of the reported address */ short _addr_lsb; /* LSB of the reported address */
struct {
void __user *_lower;
void __user *_upper;
} _addr_bnd;
} _sigfault; } _sigfault;
/* SIGPOLL */ /* SIGPOLL */
...@@ -131,6 +135,8 @@ typedef struct siginfo { ...@@ -131,6 +135,8 @@ typedef struct siginfo {
#define si_trapno _sifields._sigfault._trapno #define si_trapno _sifields._sigfault._trapno
#endif #endif
#define si_addr_lsb _sifields._sigfault._addr_lsb #define si_addr_lsb _sifields._sigfault._addr_lsb
#define si_lower _sifields._sigfault._addr_bnd._lower
#define si_upper _sifields._sigfault._addr_bnd._upper
#define si_band _sifields._sigpoll._band #define si_band _sifields._sigpoll._band
#define si_fd _sifields._sigpoll._fd #define si_fd _sifields._sigpoll._fd
#ifdef __ARCH_SIGSYS #ifdef __ARCH_SIGSYS
...@@ -199,7 +205,8 @@ typedef struct siginfo { ...@@ -199,7 +205,8 @@ typedef struct siginfo {
*/ */
#define SEGV_MAPERR (__SI_FAULT|1) /* address not mapped to object */ #define SEGV_MAPERR (__SI_FAULT|1) /* address not mapped to object */
#define SEGV_ACCERR (__SI_FAULT|2) /* invalid permissions for mapped object */ #define SEGV_ACCERR (__SI_FAULT|2) /* invalid permissions for mapped object */
#define NSIGSEGV 2 #define SEGV_BNDERR (__SI_FAULT|3) /* failed address bound checks */
#define NSIGSEGV 3
/* /*
* SIGBUS si_codes * SIGBUS si_codes
......
...@@ -179,4 +179,10 @@ struct prctl_mm_map { ...@@ -179,4 +179,10 @@ struct prctl_mm_map {
#define PR_SET_THP_DISABLE 41 #define PR_SET_THP_DISABLE 41
#define PR_GET_THP_DISABLE 42 #define PR_GET_THP_DISABLE 42
/*
* Tell the kernel to start/stop helping userspace manage bounds tables.
*/
#define PR_MPX_ENABLE_MANAGEMENT 43
#define PR_MPX_DISABLE_MANAGEMENT 44
#endif /* _LINUX_PRCTL_H */ #endif /* _LINUX_PRCTL_H */
...@@ -2755,6 +2755,10 @@ int copy_siginfo_to_user(siginfo_t __user *to, const siginfo_t *from) ...@@ -2755,6 +2755,10 @@ int copy_siginfo_to_user(siginfo_t __user *to, const siginfo_t *from)
*/ */
if (from->si_code == BUS_MCEERR_AR || from->si_code == BUS_MCEERR_AO) if (from->si_code == BUS_MCEERR_AR || from->si_code == BUS_MCEERR_AO)
err |= __put_user(from->si_addr_lsb, &to->si_addr_lsb); err |= __put_user(from->si_addr_lsb, &to->si_addr_lsb);
#endif
#ifdef SEGV_BNDERR
err |= __put_user(from->si_lower, &to->si_lower);
err |= __put_user(from->si_upper, &to->si_upper);
#endif #endif
break; break;
case __SI_CHLD: case __SI_CHLD:
......
...@@ -91,6 +91,12 @@ ...@@ -91,6 +91,12 @@
#ifndef SET_TSC_CTL #ifndef SET_TSC_CTL
# define SET_TSC_CTL(a) (-EINVAL) # define SET_TSC_CTL(a) (-EINVAL)
#endif #endif
#ifndef MPX_ENABLE_MANAGEMENT
# define MPX_ENABLE_MANAGEMENT(a) (-EINVAL)
#endif
#ifndef MPX_DISABLE_MANAGEMENT
# define MPX_DISABLE_MANAGEMENT(a) (-EINVAL)
#endif
/* /*
* this is where the system-wide overflow UID and GID are defined, for * this is where the system-wide overflow UID and GID are defined, for
...@@ -2203,6 +2209,12 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, ...@@ -2203,6 +2209,12 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
me->mm->def_flags &= ~VM_NOHUGEPAGE; me->mm->def_flags &= ~VM_NOHUGEPAGE;
up_write(&me->mm->mmap_sem); up_write(&me->mm->mmap_sem);
break; break;
case PR_MPX_ENABLE_MANAGEMENT:
error = MPX_ENABLE_MANAGEMENT(me);
break;
case PR_MPX_DISABLE_MANAGEMENT:
error = MPX_DISABLE_MANAGEMENT(me);
break;
default: default:
error = -EINVAL; error = -EINVAL;
break; break;
......
...@@ -2601,6 +2601,8 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len) ...@@ -2601,6 +2601,8 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
detach_vmas_to_be_unmapped(mm, vma, prev, end); detach_vmas_to_be_unmapped(mm, vma, prev, end);
unmap_region(mm, vma, prev, start, end); unmap_region(mm, vma, prev, start, end);
arch_unmap(mm, vma, start, end);
/* Fix up all other VM information */ /* Fix up all other VM information */
remove_vma_list(mm, vma); remove_vma_list(mm, vma);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment