commit b064131bdf099d3647b4501e5d15391e1e9623e6 Author: Mark Wielaard Date: Thu May 30 00:29:58 2019 +0200 linux x86 and amd64 memory protection key syscalls. This implements minimal support for the pkey_alloc, pkey_free and pkey_mprotect syscalls. pkey_alloc will simply indicate that pkeys are not supported. pkey_free always fails. pkey_mprotect works just like mprotect if the special pkey -1 is provided. https://bugs.kde.org/show_bug.cgi?id=408091 diff --git a/coregrind/m_syswrap/priv_syswrap-generic.h b/coregrind/m_syswrap/priv_syswrap-generic.h index 88530f0..3e1c8b6 100644 --- a/coregrind/m_syswrap/priv_syswrap-generic.h +++ b/coregrind/m_syswrap/priv_syswrap-generic.h @@ -106,6 +106,10 @@ extern Bool ML_(handle_auxv_open)(SyscallStatus *status, const HChar *filename, int flags); +/* Helper function for generic mprotect and linux pkey_mprotect. */ +extern void handle_sys_mprotect (ThreadId tid, SyscallStatus *status, + Addr *addr, SizeT *len, Int *prot); + DECL_TEMPLATE(generic, sys_ni_syscall); // * P -- unimplemented DECL_TEMPLATE(generic, sys_exit); DECL_TEMPLATE(generic, sys_fork); diff --git a/coregrind/m_syswrap/priv_syswrap-linux.h b/coregrind/m_syswrap/priv_syswrap-linux.h index 5cf5407..2471524 100644 --- a/coregrind/m_syswrap/priv_syswrap-linux.h +++ b/coregrind/m_syswrap/priv_syswrap-linux.h @@ -299,6 +299,11 @@ DECL_TEMPLATE(linux, sys_bpf); // Linux-specific (new in Linux 4.11) DECL_TEMPLATE(linux, sys_statx); +// Linux-specific memory protection key syscalls (since Linux 4.9) +DECL_TEMPLATE(linux, sys_pkey_alloc); +DECL_TEMPLATE(linux, sys_pkey_free); +DECL_TEMPLATE(linux, sys_pkey_mprotect); + /* --------------------------------------------------------------------- Wrappers for sockets and ipc-ery. These are split into standalone procedures because x86-linux hides them inside multiplexors diff --git a/coregrind/m_syswrap/syswrap-amd64-linux.c b/coregrind/m_syswrap/syswrap-amd64-linux.c index d4fe413..2d6b95f 100644 --- a/coregrind/m_syswrap/syswrap-amd64-linux.c +++ b/coregrind/m_syswrap/syswrap-amd64-linux.c @@ -863,6 +863,10 @@ static SyscallTableEntry syscall_table[] = { LINX_(__NR_membarrier, sys_membarrier), // 324 LINX_(__NR_copy_file_range, sys_copy_file_range), // 326 + + LINXY(__NR_pkey_mprotect, sys_pkey_mprotect), // 329 + LINX_(__NR_pkey_alloc, sys_pkey_alloc), // 330 + LINX_(__NR_pkey_free, sys_pkey_free), // 331 }; SyscallTableEntry* ML_(get_linux_syscall_entry) ( UInt sysno ) diff --git a/coregrind/m_syswrap/syswrap-generic.c b/coregrind/m_syswrap/syswrap-generic.c index 0b64919..01191f6 100644 --- a/coregrind/m_syswrap/syswrap-generic.c +++ b/coregrind/m_syswrap/syswrap-generic.c @@ -3842,12 +3842,28 @@ PRE(sys_mprotect) PRE_REG_READ3(long, "mprotect", unsigned long, addr, vki_size_t, len, unsigned long, prot); - if (!ML_(valid_client_addr)(ARG1, ARG2, tid, "mprotect")) { + Addr addr = ARG1; + SizeT len = ARG2; + Int prot = ARG3; + + handle_sys_mprotect (tid, status, &addr, &len, &prot); + + ARG1 = addr; + ARG2 = len; + ARG3 = prot; +} +/* This will be called from the generic mprotect, or the linux specific + pkey_mprotect. Pass pointers to ARG1, ARG2 and ARG3 as addr, len and prot, + they might be adjusted and have to assigned back to ARG1, ARG2 and ARG3. */ +void handle_sys_mprotect(ThreadId tid, SyscallStatus* status, + Addr *addr, SizeT *len, Int *prot) +{ + if (!ML_(valid_client_addr)(*addr, *len, tid, "mprotect")) { SET_STATUS_Failure( VKI_ENOMEM ); } #if defined(VKI_PROT_GROWSDOWN) else - if (ARG3 & (VKI_PROT_GROWSDOWN|VKI_PROT_GROWSUP)) { + if (*prot & (VKI_PROT_GROWSDOWN|VKI_PROT_GROWSUP)) { /* Deal with mprotects on growable stack areas. The critical files to understand all this are mm/mprotect.c @@ -3862,8 +3878,8 @@ PRE(sys_mprotect) The sanity check provided by the kernel is that the vma must have the VM_GROWSDOWN/VM_GROWSUP flag set as appropriate. */ - UInt grows = ARG3 & (VKI_PROT_GROWSDOWN|VKI_PROT_GROWSUP); - NSegment const *aseg = VG_(am_find_nsegment)(ARG1); + UInt grows = *prot & (VKI_PROT_GROWSDOWN|VKI_PROT_GROWSUP); + NSegment const *aseg = VG_(am_find_nsegment)(*addr); NSegment const *rseg; vg_assert(aseg); @@ -3874,10 +3890,10 @@ PRE(sys_mprotect) && rseg->kind == SkResvn && rseg->smode == SmUpper && rseg->end+1 == aseg->start) { - Addr end = ARG1 + ARG2; - ARG1 = aseg->start; - ARG2 = end - aseg->start; - ARG3 &= ~VKI_PROT_GROWSDOWN; + Addr end = *addr + *len; + *addr = aseg->start; + *len = end - aseg->start; + *prot &= ~VKI_PROT_GROWSDOWN; } else { SET_STATUS_Failure( VKI_EINVAL ); } @@ -3887,8 +3903,8 @@ PRE(sys_mprotect) && rseg->kind == SkResvn && rseg->smode == SmLower && aseg->end+1 == rseg->start) { - ARG2 = aseg->end - ARG1 + 1; - ARG3 &= ~VKI_PROT_GROWSUP; + *len = aseg->end - *addr + 1; + *prot &= ~VKI_PROT_GROWSUP; } else { SET_STATUS_Failure( VKI_EINVAL ); } diff --git a/coregrind/m_syswrap/syswrap-linux.c b/coregrind/m_syswrap/syswrap-linux.c index 810ca24..5452b8d 100644 --- a/coregrind/m_syswrap/syswrap-linux.c +++ b/coregrind/m_syswrap/syswrap-linux.c @@ -12120,6 +12120,76 @@ PRE(sys_copy_file_range) } } +PRE(sys_pkey_alloc) +{ + PRINT("pkey_alloc (%lu, %lu)", ARG1, ARG2); + + PRE_REG_READ2(long, "pkey_alloc", + unsigned long, "flags", + unsigned long, "access_rights"); + + /* The kernel says: pkey_alloc() is always safe to call regardless of + whether or not the operating system supports protection keys. It can be + used in lieu of any other mechanism for detecting pkey support and will + simply fail with the error ENOSPC if the operating system has no pkey + support. + + So we simply always return ENOSPC to signal memory protection keys are + not supported under valgrind, unless there are unknown flags, then we + return EINVAL. */ + unsigned long pkey_flags = ARG1; + if (pkey_flags != 0) + SET_STATUS_Failure( VKI_EINVAL ); + else + SET_STATUS_Failure( VKI_ENOSPC ); +} + +PRE(sys_pkey_free) +{ + PRINT("pkey_free (%" FMT_REGWORD "u )", ARG1); + + PRE_REG_READ1(long, "pkey_free", + unsigned long, "pkey"); + + /* Since pkey_alloc () can never succeed, see above, freeing any pkey is + always an error. */ + SET_STATUS_Failure( VKI_EINVAL ); +} + +PRE(sys_pkey_mprotect) +{ + PRINT("sys_pkey_mprotect ( %#" FMT_REGWORD "x, %" FMT_REGWORD "u, %" + FMT_REGWORD "u %" FMT_REGWORD "u )", ARG1, ARG2, ARG3, ARG4); + PRE_REG_READ4(long, "pkey_mprotect", + unsigned long, addr, vki_size_t, len, unsigned long, prot, + unsigned long, pkey); + + Addr addr = ARG1; + SizeT len = ARG2; + Int prot = ARG3; + Int pkey = ARG4; + + /* Since pkey_alloc () can never succeed, see above, any pkey is + invalid. Except for -1, then pkey_mprotect acts just like mprotect. */ + if (pkey != -1) + SET_STATUS_Failure( VKI_EINVAL ); + else + handle_sys_mprotect (tid, status, &addr, &len, &prot); + + ARG1 = addr; + ARG2 = len; + ARG3 = prot; +} + +POST(sys_pkey_mprotect) +{ + Addr addr = ARG1; + SizeT len = ARG2; + Int prot = ARG3; + + ML_(notify_core_and_tool_of_mprotect)(addr, len, prot); +} + #undef PRE #undef POST diff --git a/coregrind/m_syswrap/syswrap-x86-linux.c b/coregrind/m_syswrap/syswrap-x86-linux.c index ad54cf6..3829fa4 100644 --- a/coregrind/m_syswrap/syswrap-x86-linux.c +++ b/coregrind/m_syswrap/syswrap-x86-linux.c @@ -1608,6 +1608,9 @@ static SyscallTableEntry syscall_table[] = { LINX_(__NR_copy_file_range, sys_copy_file_range), // 377 + LINXY(__NR_pkey_mprotect, sys_pkey_mprotect), // 380 + LINX_(__NR_pkey_alloc, sys_pkey_alloc), // 381 + LINX_(__NR_pkey_free, sys_pkey_free), // 382 LINXY(__NR_statx, sys_statx), // 383 /* Explicitly not supported on i386 yet. */