Linux: Implement interfaces for memory protection keys

This adds system call wrappers for pkey_alloc, pkey_free, pkey_mprotect,
and x86-64 implementations of pkey_get and pkey_set, which abstract over
the PKRU CPU register and hide the actual number of memory protection
keys supported by the CPU.  pkey_mprotect with a -1 key is implemented
using mprotect, so it will work even if the kernel does not support the
pkey_mprotect system call.

The system call wrapers use unsigned int instead of unsigned long for
parameters, so that no special treatment for x32 is needed.  The flags
argument is currently unused, and the access rights bit mask is limited
to two bits by the current PKRU register layout anyway.

Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
This commit is contained in:
Florian Weimer 2017-12-05 15:20:30 +01:00
parent da616c1496
commit 446d22e91d
50 changed files with 1179 additions and 4 deletions

View file

@ -1,3 +1,37 @@
2017-12-05 Florian Weimer <fweimer@redhat.com>
Linux: Implement interfaces for memory protection keys
* support/Makefile (libsupport-routines): Add xraise, xsigaction,
xsignal, xsysconf.
* support/xsignal.h (xraise, xsignal, xsigaction): Declare.
* support/xunistd.h (xsysconf): Declare.
* support/xraise.c: New file.
* support/xsigaction.c: Likewise.
* support/xsignal.c: Likewise.
* support/xsysconf.c: Likewise.
* sysdeps/unix/sysv/linux/Makefile [misc] (routines): Add
pkey_set, pkey_get, pkey_mprotect.
[misc] (tests): Add tst-pkey.
(tst-pkey): Link with -lpthread.
* sysdeps/unix/sysv/linux/Versions (GLIBC_2.27): Add pkey_alloc,
pkey_free, pkey_set, pkey_get, pkey_mprotect.
* sysdeps/unix/sysv/linux/bits/mman-linux.h (PKEY_DISABLE_ACCESS)
(PKEY_DISABLE_WRITE): Define.
(pkey_alloc, pkey_free, pkey_set, pkey_get, pkey_mprotect):
Declare.
* sysdeps/unix/sysv/linux/bits/siginfo-consts.h (SEGV_BNDERR)
(SEGV_PKUERR): Add.
* sysdeps/unix/sysv/linux/pkey_get.c: New file.
* sysdeps/unix/sysv/linux/pkey_set.c: Likewise.
* sysdeps/unix/sysv/linux/pkey_mprotect.c: Likewise.
* sysdeps/unix/sysv/linux/syscalls.list (pkey_alloc, pkey_free):
Add.
* sysdeps/unix/sysv/linux/tst-pkey.c: New file.
* sysdeps/unix/sysv/linux/x86/arch-pkey.h: Likewise.
* sysdeps/unix/sysv/linux/x86/pkey_get.c: Likewise.
* sysdeps/unix/sysv/linux/x86/pkey_set.c: Likewise.
* sysdeps/unix/sysv/linux/**.abilist: Update.
2017-12-05 Florian Weimer <fweimer@redhat.com>
* support/tst-test_compare.c (subprocess): Use long long instead

4
NEWS
View file

@ -43,6 +43,10 @@ Major new features:
* glibc now implements the memfd_create and mlock2 functions on Linux.
* Support for memory protection keys was added. The <sys/mman.h> header now
declares the functions pkey_alloc, pkey_free, pkey_mprotect, pkey_set,
pkey_get.
Deprecated and removed features, and other changes affecting compatibility:
* On GNU/Linux, the obsolete Linux constant PTRACE_SEIZE_DEVEL is no longer

View file

@ -3171,6 +3171,238 @@ process memory, no matter how it was allocated. However, portable use
of the function requires that it is only used with memory regions
returned by @code{mmap} or @code{mmap64}.
@subsection Memory Protection Keys
@cindex memory protection key
@cindex protection key
@cindex MPK
On some systems, further restrictions can be added to specific pages
using @dfn{memory protection keys}. These restrictions work as follows:
@itemize @bullet
@item
All memory pages are associated with a protection key. The default
protection key does not cause any additional protections to be applied
during memory accesses. New keys can be allocated with the
@code{pkey_alloc} function, and applied to pages using
@code{pkey_mprotect}.
@item
Each thread has a set of separate access right restriction for each
protection key. These access rights can be manipulated using the
@code{pkey_set} and @code{pkey_get} functions.
@item
During a memory access, the system obtains the protection key for the
accessed page and uses that to determine the applicable access rights,
as configured for the current thread. If the access is restricted, a
segmentation fault is the result ((@pxref{Program Error Signals}).
These checks happen in addition to the @code{PROT_}* protection flags
set by @code{mprotect} or @code{pkey_mprotect}.
@end itemize
New threads and subprocesses inherit the access rights of the current
thread. If a protection key is allocated subsequently, existing threads
(except the current) will use an unspecified system default for the
access rights associated with newly allocated keys.
Upon entering a signal handler, the system resets the access rights of
the current thread so that pages with the default key can be accessed,
but the access rights for other protection keys are unspecified.
Applications are expected to allocate a key once using
@code{pkey_alloc}, and apply the key to memory regions which need
special protection with @code{pkey_mprotect}:
@smallexample
int key = pkey_alloc (0, PKEY_DISABLE_ACCESS);
if (key < 0)
/* Perform error checking, including fallback for lack of support. */
...;
/* Apply the key to a special memory region used to store critical
data. */
if (pkey_mprotect (region, region_length,
PROT_READ | PROT_WRITE, key) < 0)
...; /* Perform error checking (generally fatal). */
@end smallexample
If the key allocation fails due to lack of support for memory protection
keys, the @code{pkey_mprotect} call can usually be skipped. In this
case, the region will not be protected by default. It is also possible
to call @code{pkey_mprotect} with a key value of @math{-1}, in which
case it will behave in the same way as @code{mprotect}.
After key allocation assignment to memory pages, @code{pkey_set} can be
used to temporarily acquire access to the memory region and relinquish
it again:
@smallexample
if (key >= 0 && pkey_set (key, 0) < 0)
...; /* Perform error checking (generally fatal). */
/* At this point, the current thread has read-write access to the
memory region. */
...
/* Revoke access again. */
if (key >= 0 && pkey_set (key, PKEY_DISABLE_ACCESS) < 0)
...; /* Perform error checking (generally fatal). */
@end smallexample
In this example, a negative key value indicates that no key had been
allocated, which means that the system lacks support for memory
protection keys and it is not necessary to change the the access rights
of the current thread (because it always has access).
Compared to using @code{mprotect} to change the page protection flags,
this approach has two advantages: It is thread-safe in the sense that
the access rights are only changed for the current thread, so another
thread which changes its own access rights concurrently to gain access
to the mapping will not suddenly see its access rights revoked. And
@code{pkey_set} typically does not involve a call into the kernel and a
context switch, so it is more efficient.
@deftypefun int pkey_alloc (unsigned int @var{flags}, unsigned int @var{restrictions})
@standards{Linux, sys/mman.h}
@safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@acucorrupt{}}}
Allocate a new protection key. The @var{flags} argument is reserved and
must be zero. The @var{restrictions} argument specifies access rights
which are applied to the current thread (as if with @code{pkey_set}
below). Access rights of other threads are not changed.
The function returns the new protection key, a non-negative number, or
@math{-1} on error.
The following @code{errno} error conditions are defined for this
function:
@table @code
@item ENOSYS
The system does not implement memory protection keys.
@item EINVAL
The @var{flags} argument is not zero.
The @var{restrictions} argument is invalid.
The system does not implement memory protection keys or runs in a mode
in which memory protection keys are disabled.
@item ENOSPC
All available protection keys already have been allocated.
@end table
@end deftypefun
@deftypefun int pkey_free (int @var{key})
@standards{Linux, sys/mman.h}
@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
Deallocate the protection key, so that it can be reused by
@code{pkey_alloc}.
Calling this function does not change the access rights of the freed
protection key. The calling thread and other threads may retain access
to it, even if it is subsequently allocated again. For this reason, it
is not recommended to call the @code{pkey_free} function.
@table @code
@item ENOSYS
The system does not implement memory protection keys.
@item EINVAL
The @var{key} argument is not a valid protection key.
@end table
@end deftypefun
@deftypefun int pkey_mprotect (void *@var{address}, size_t @var{length}, int @var{protection}, int @var{key})
@standards{Linux, sys/mman.h}
@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
Similar to @code{mprotect}, but also set the memory protection key for
the memory region to @code{key}.
Some systems use memory protection keys to emulate certain combinations
of @var{protection} flags. Under such circumstances, specifying an
explicit protection key may behave as if additional flags have been
specified in @var{protection}, even though this does not happen with the
default protection key. For example, some systems can support
@code{PROT_EXEC}-only mappings only with a default protection key, and
memory with a key which was allocated using @code{pkey_alloc} will still
be readable if @code{PROT_EXEC} is specified without @code{PROT_READ}.
If @var{key} is @math{-1}, the default protection key is applied to the
mapping, just as if @code{mprotect} had been called.
The @code{pkey_mprotect} function returns @math{0} on success and
@math{-1} on failure. The same @code{errno} error conditions as for
@code{mprotect} are defined for this function, with the following
addition:
@table @code
@item EINVAL
The @var{key} argument is not @math{-1} or a valid memory protection
key allocated using @code{pkey_alloc}.
@item ENOSYS
The system does not implement memory protection keys, and @var{key} is
not @math{-1}.
@end table
@end deftypefun
@deftypefun int pkey_set (int @var{key}, unsigned int @var{rights})
@standards{Linux, sys/mman.h}
@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
Change the access rights of the current thread for memory pages with the
protection key @var{key} to @var{rights}. If @var{rights} is zero, no
additional access restrictions on top of the page protection flags are
applied. Otherwise, @var{rights} is a combination of the following
flags:
@vtable @code
@item PKEY_DISABLE_WRITE
@standards{Linux, sys/mman.h}
Subsequent attempts to write to memory with the specified protection
key will fault.
@item PKEY_DISABLE_ACCESS
@standards{Linux, sys/mman.h}
Subsequent attempts to write to or read from memory with the specified
protection key will fault.
@end vtable
Operations not specified as flags are not restricted. In particular,
this means that the memory region will remain executable if it was
mapped with the @code{PROT_EXEC} protection flag and
@code{PKEY_DISABLE_ACCESS} has been specified.
Calling the @code{pkey_set} function with a protection key which was not
allocated by @code{pkey_alloc} results in undefined behavior. This
means that calling this function on systems which do not support memory
protection keys is undefined.
The @code{pkey_set} function returns @math{0} on success and @math{-1}
on failure.
The following @code{errno} error conditions are defined for this
function:
@table @code
@item EINVAL
The system does not support the access rights restrictions expressed in
the @var{rights} argument.
@end table
@end deftypefun
@deftypefun int pkey_get (int @var{key})
@standards{Linux, sys/mman.h}
@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
Return the access rights of the current thread for memory pages with
protection key @var{key}. The return value is zero or a combination of
the @code{PKEY_DISABLE_}* flags; see the @code{pkey_set} function.
Calling the @code{pkey_get} function with a protection key which was not
allocated by @code{pkey_alloc} results in undefined behavior. This
means that calling this function on systems which do not support memory
protection keys is undefined.
@end deftypefun
@node Locking Pages
@section Locking Pages
@cindex locking pages

View file

@ -87,8 +87,8 @@ libsupport-routines = \
xpthread_attr_destroy \
xpthread_attr_init \
xpthread_attr_setdetachstate \
xpthread_attr_setstacksize \
xpthread_attr_setguardsize \
xpthread_attr_setstacksize \
xpthread_barrier_destroy \
xpthread_barrier_init \
xpthread_barrier_wait \
@ -119,14 +119,18 @@ libsupport-routines = \
xpthread_sigmask \
xpthread_spin_lock \
xpthread_spin_unlock \
xraise \
xreadlink \
xrealloc \
xrecvfrom \
xsendto \
xsetsockopt \
xsigaction \
xsignal \
xsocket \
xstrdup \
xstrndup \
xsysconf \
xunlink \
xwaitpid \
xwrite \

27
support/xraise.c Normal file
View file

@ -0,0 +1,27 @@
/* Error-checking wrapper for raise.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <support/check.h>
#include <support/xsignal.h>
void
xraise (int sig)
{
if (raise (sig) != 0)
FAIL_EXIT1 ("raise (%d): %m" , sig);
}

27
support/xsigaction.c Normal file
View file

@ -0,0 +1,27 @@
/* Error-checking wrapper for sigaction.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <support/check.h>
#include <support/xsignal.h>
void
xsigaction (int sig, const struct sigaction *newact, struct sigaction *oldact)
{
if (sigaction (sig, newact, oldact))
FAIL_EXIT1 ("sigaction (%d): %m" , sig);
}

29
support/xsignal.c Normal file
View file

@ -0,0 +1,29 @@
/* Error-checking wrapper for signal.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <support/check.h>
#include <support/xsignal.h>
sighandler_t
xsignal (int sig, sighandler_t handler)
{
sighandler_t result = signal (sig, handler);
if (result == SIG_ERR)
FAIL_EXIT1 ("signal (%d, %p): %m", sig, handler);
return result;
}

View file

@ -24,6 +24,14 @@
__BEGIN_DECLS
/* The following functions call the corresponding libc functions and
terminate the process on error. */
void xraise (int sig);
sighandler_t xsignal (int sig, sighandler_t handler);
void xsigaction (int sig, const struct sigaction *newact,
struct sigaction *oldact);
/* The following functions call the corresponding libpthread functions
and terminate the process on error. */

36
support/xsysconf.c Normal file
View file

@ -0,0 +1,36 @@
/* Error-checking wrapper for sysconf.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <errno.h>
#include <support/check.h>
#include <support/xunistd.h>
long
xsysconf (int name)
{
/* Detect errors by a changed errno value, in case -1 is a valid
value. Make sure that the caller does not see the zero value for
errno. */
int old_errno = errno;
errno = 0;
long result = sysconf (name);
if (errno != 0)
FAIL_EXIT1 ("sysconf (%d): %m", name);
errno = old_errno;
return result;
}

View file

@ -39,6 +39,7 @@ void xstat (const char *path, struct stat64 *);
void xmkdir (const char *path, mode_t);
void xchroot (const char *path);
void xunlink (const char *path);
long xsysconf (int name);
/* Read the link at PATH. The caller should free the returned string
with free. */

View file

@ -18,7 +18,7 @@ sysdep_routines += clone umount umount2 readahead \
setfsuid setfsgid epoll_pwait signalfd \
eventfd eventfd_read eventfd_write prlimit \
personality epoll_wait tee vmsplice splice \
open_by_handle_at mlock2
open_by_handle_at mlock2 pkey_mprotect pkey_set pkey_get
CFLAGS-gethostid.c = -fexceptions
CFLAGS-tee.c = -fexceptions -fasynchronous-unwind-tables
@ -44,7 +44,7 @@ sysdep_headers += sys/mount.h sys/acct.h sys/sysctl.h \
tests += tst-clone tst-clone2 tst-clone3 tst-fanotify tst-personality \
tst-quota tst-sync_file_range tst-sysconf-iov_max tst-ttyname \
test-errno-linux tst-memfd_create tst-mlock2
test-errno-linux tst-memfd_create tst-mlock2 tst-pkey
# Generate the list of SYS_* macros for the system calls (__NR_*
# macros). The file syscall-names.list contains all possible system
@ -92,6 +92,8 @@ $(objpfx)tst-syscall-list.out: \
# Separate object file for access to the constant from the UAPI header.
$(objpfx)tst-sysconf-iov_max: $(objpfx)tst-sysconf-iov_max-uapi.o
$(objpfx)tst-pkey: $(shared-thread-library)
endif # $(subdir) == misc
ifeq ($(subdir),time)

View file

@ -169,6 +169,7 @@ libc {
GLIBC_2.27 {
memfd_create;
mlock2;
pkey_alloc; pkey_free; pkey_set; pkey_get; pkey_mprotect;
}
GLIBC_PRIVATE {
# functions used in other libraries

View file

@ -2108,6 +2108,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf128 F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof128 F

View file

@ -2019,6 +2019,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf128 F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof128 F

View file

@ -109,6 +109,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.4 GLIBC_2.4 A
GLIBC_2.4 _Exit F
GLIBC_2.4 _IO_2_1_stderr_ D 0xa0

View file

@ -33,6 +33,12 @@
# define MLOCK_ONFAULT 1U
# endif
/* Access rights for pkey_alloc. */
# ifndef PKEY_DISABLE_ACCESS
# define PKEY_DISABLE_ACCESS 0x1
# define PKEY_DISABLE_WRITE 0x2
# endif
__BEGIN_DECLS
/* Create a new memory file descriptor. NAME is a name for debugging.
@ -43,6 +49,28 @@ int memfd_create (const char *__name, unsigned int __flags) __THROW;
memory. FLAGS is a combination of the MLOCK_* flags above. */
int mlock2 (const void *__addr, size_t __length, unsigned int __flags) __THROW;
/* Allocate a new protection key, with the PKEY_DISABLE_* bits
specified in ACCESS_RIGHTS. The protection key mask for the
current thread is updated to match the access privilege for the new
key. */
int pkey_alloc (unsigned int __flags, unsigned int __access_rights) __THROW;
/* Update the access rights for the current thread for KEY, which must
have been allocated using pkey_alloc. */
int pkey_set (int __key, unsigned int __access_rights) __THROW;
/* Return the access rights for the current thread for KEY, which must
have been allocated using pkey_alloc. */
int pkey_get (int _key) __THROW;
/* Free an allocated protection key, which must have been allocated
using pkey_alloc. */
int pkey_free (int __key) __THROW;
/* Apply memory protection flags for KEY to the specified address
range. */
int pkey_mprotect (void *__addr, size_t __len, int __prot, int __pkey) __THROW;
__END_DECLS
#endif /* __USE_GNU */

View file

@ -111,8 +111,12 @@ enum
{
SEGV_MAPERR = 1, /* Address not mapped to object. */
# define SEGV_MAPERR SEGV_MAPERR
SEGV_ACCERR /* Invalid permissions for mapped object. */
SEGV_ACCERR, /* Invalid permissions for mapped object. */
# define SEGV_ACCERR SEGV_ACCERR
SEGV_BNDERR, /* Bounds checking failure. */
# define SEGV_BNDERR SEGV_BNDERR
SEGV_PKUERR /* Protection key checking failure. */
# define SEGV_PKUERR SEGV_PKUERR
};
/* `si_code' values for SIGBUS signal. */

View file

@ -1873,6 +1873,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.3 GLIBC_2.3 A
GLIBC_2.3 __ctype_b_loc F
GLIBC_2.3 __ctype_tolower_loc F

View file

@ -2038,6 +2038,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof64x F
GLIBC_2.27 strtof64x_l F

View file

@ -1902,6 +1902,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof64x F
GLIBC_2.27 strtof64x_l F

View file

@ -110,6 +110,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.4 GLIBC_2.4 A
GLIBC_2.4 _Exit F
GLIBC_2.4 _IO_2_1_stderr_ D 0x98

View file

@ -1987,6 +1987,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.3 GLIBC_2.3 A
GLIBC_2.3 __ctype_b_loc F
GLIBC_2.3 __ctype_tolower_loc F

View file

@ -2108,3 +2108,8 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F

View file

@ -1962,6 +1962,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.3 GLIBC_2.3 A
GLIBC_2.3 __ctype_b_loc F
GLIBC_2.3 __ctype_tolower_loc F

View file

@ -1960,6 +1960,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.3 GLIBC_2.3 A
GLIBC_2.3 __ctype_b_loc F
GLIBC_2.3 __ctype_tolower_loc F

View file

@ -1958,6 +1958,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf128 F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof128 F

View file

@ -1953,6 +1953,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf128 F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof128 F

View file

@ -2149,3 +2149,8 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F

View file

@ -0,0 +1,26 @@
/* Obtaining the thread memory protection key, generic stub.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <errno.h>
int
pkey_get (int key)
{
__set_errno (ENOSYS);
return -1;
}

View file

@ -0,0 +1,37 @@
/* mprotect with a memory protection key.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <errno.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sysdep.h>
int
pkey_mprotect (void *addr, size_t len, int prot, int pkey)
{
if (pkey == -1)
/* If the key is -1, the system call is precisely equivalent to
mprotect. */
return __mprotect (addr, len, prot);
#ifdef __NR_pkey_mprotect
return INLINE_SYSCALL_CALL (pkey_mprotect, addr, len, prot, pkey);
#else
__set_errno (ENOSYS);
return -1;
#endif
}

View file

@ -0,0 +1,26 @@
/* Changing the thread memory protection key, generic stub.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <errno.h>
int
pkey_set (int key, unsigned int access_rights)
{
__set_errno (ENOSYS);
return -1;
}

View file

@ -1991,6 +1991,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.3 GLIBC_2.3 A
GLIBC_2.3 __ctype_b_loc F
GLIBC_2.3 __ctype_tolower_loc F

View file

@ -1996,6 +1996,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.3 GLIBC_2.3 A
GLIBC_2.3 __ctype_b_loc F
GLIBC_2.3 __ctype_tolower_loc F

View file

@ -2203,6 +2203,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof64x F
GLIBC_2.27 strtof64x_l F

View file

@ -110,6 +110,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.3 GLIBC_2.3 A
GLIBC_2.3 _Exit F
GLIBC_2.3 _IO_2_1_stderr_ D 0xe0

View file

@ -1991,6 +1991,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf128 F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof128 F

View file

@ -1892,6 +1892,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf128 F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof128 F

View file

@ -1877,6 +1877,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.3 GLIBC_2.3 A
GLIBC_2.3 __ctype_b_loc F
GLIBC_2.3 __ctype_tolower_loc F

View file

@ -1984,6 +1984,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf128 F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof128 F

View file

@ -1921,6 +1921,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf128 F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof128 F

View file

@ -110,3 +110,5 @@ setns EXTRA setns i:ii setns
process_vm_readv EXTRA process_vm_readv i:ipipii process_vm_readv
process_vm_writev EXTRA process_vm_writev i:ipipii process_vm_writev
memfd_create EXTRA memfd_create i:si memfd_create
pkey_alloc EXTRA pkey_alloc i:ii pkey_alloc
pkey_free EXTRA pkey_free i:i pkey_free

View file

@ -2115,3 +2115,8 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F

View file

@ -2115,3 +2115,8 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F

View file

@ -2115,3 +2115,8 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F

View file

@ -0,0 +1,399 @@
/* Tests for memory protection keys.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <errno.h>
#include <inttypes.h>
#include <setjmp.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <support/check.h>
#include <support/support.h>
#include <support/test-driver.h>
#include <support/xsignal.h>
#include <support/xthread.h>
#include <support/xunistd.h>
#include <sys/mman.h>
/* Used to force threads to wait until the main thread has set up the
keys as intended. */
static pthread_barrier_t barrier;
/* The keys used for testing. These have been allocated with access
rights set based on their array index. */
enum { key_count = 4 };
static int keys[key_count];
static volatile int *pages[key_count];
/* Used to report results from the signal handler. */
static volatile void *sigsegv_addr;
static volatile int sigsegv_code;
static volatile int sigsegv_pkey;
static sigjmp_buf sigsegv_jmp;
/* Used to handle expected read or write faults. */
static void
sigsegv_handler (int signum, siginfo_t *info, void *context)
{
sigsegv_addr = info->si_addr;
sigsegv_code = info->si_code;
sigsegv_pkey = info->si_pkey;
siglongjmp (sigsegv_jmp, 2);
}
static const struct sigaction sigsegv_sigaction =
{
.sa_flags = SA_RESETHAND | SA_SIGINFO,
.sa_sigaction = &sigsegv_handler,
};
/* Check if PAGE is readable (if !WRITE) or writable (if WRITE). */
static bool
check_page_access (int page, bool write)
{
/* This is needed to work around bug 22396: On x86-64, siglongjmp
does not restore the protection key access rights for the current
thread. We restore only the access rights for the keys under
test. (This is not a general solution to this problem, but it
allows testing to proceed after a fault.) */
unsigned saved_rights[key_count];
for (int i = 0; i < key_count; ++i)
saved_rights[i] = pkey_get (keys[i]);
volatile int *addr = pages[page];
if (test_verbose > 0)
{
printf ("info: checking access at %p (page %d) for %s\n",
addr, page, write ? "writing" : "reading");
}
int result = sigsetjmp (sigsegv_jmp, 1);
if (result == 0)
{
xsigaction (SIGSEGV, &sigsegv_sigaction, NULL);
if (write)
*addr = 3;
else
(void) *addr;
xsignal (SIGSEGV, SIG_DFL);
if (test_verbose > 0)
puts (" --> access allowed");
return true;
}
else
{
xsignal (SIGSEGV, SIG_DFL);
if (test_verbose > 0)
puts (" --> access denied");
TEST_COMPARE (result, 2);
TEST_COMPARE ((uintptr_t) sigsegv_addr, (uintptr_t) addr);
TEST_COMPARE (sigsegv_code, SEGV_PKUERR);
TEST_COMPARE (sigsegv_pkey, keys[page]);
for (int i = 0; i < key_count; ++i)
TEST_COMPARE (pkey_set (keys[i], saved_rights[i]), 0);
return false;
}
}
static volatile sig_atomic_t sigusr1_handler_ran;
/* Used to check that access is revoked in signal handlers. */
static void
sigusr1_handler (int signum)
{
TEST_COMPARE (signum, SIGUSR1);
for (int i = 0; i < key_count; ++i)
TEST_COMPARE (pkey_get (keys[i]), PKEY_DISABLE_ACCESS);
sigusr1_handler_ran = 1;
}
/* Used to report results from other threads. */
struct thread_result
{
int access_rights[key_count];
pthread_t next_thread;
};
/* Return the thread's access rights for the keys under test. */
static void *
get_thread_func (void *closure)
{
struct thread_result *result = xmalloc (sizeof (*result));
for (int i = 0; i < key_count; ++i)
result->access_rights[i] = pkey_get (keys[i]);
memset (&result->next_thread, 0, sizeof (result->next_thread));
return result;
}
/* Wait for initialization and then check that the current thread does
not have access through the keys under test. */
static void *
delayed_thread_func (void *closure)
{
bool check_access = *(bool *) closure;
pthread_barrier_wait (&barrier);
struct thread_result *result = get_thread_func (NULL);
if (check_access)
{
/* Also check directly. This code should not run with other
threads in parallel because of the SIGSEGV handler which is
installed by check_page_access. */
for (int i = 0; i < key_count; ++i)
{
TEST_VERIFY (!check_page_access (i, false));
TEST_VERIFY (!check_page_access (i, true));
}
}
result->next_thread = xpthread_create (NULL, get_thread_func, NULL);
return result;
}
static int
do_test (void)
{
long pagesize = xsysconf (_SC_PAGESIZE);
/* pkey_mprotect with key -1 should work even when there is no
protection key support. */
{
int *page = xmmap (NULL, pagesize, PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE, -1);
TEST_COMPARE (pkey_mprotect (page, pagesize, PROT_READ | PROT_WRITE, -1),
0);
volatile int *vpage = page;
*vpage = 5;
TEST_COMPARE (*vpage, 5);
xmunmap (page, pagesize);
}
xpthread_barrier_init (&barrier, NULL, 2);
bool delayed_thread_check_access = true;
pthread_t delayed_thread = xpthread_create
(NULL, &delayed_thread_func, &delayed_thread_check_access);
keys[0] = pkey_alloc (0, 0);
if (keys[0] < 0)
{
if (errno == ENOSYS)
FAIL_UNSUPPORTED
("kernel does not support memory protection keys");
if (errno == EINVAL)
FAIL_UNSUPPORTED
("CPU does not support memory protection keys: %m");
FAIL_EXIT1 ("pkey_alloc: %m");
}
TEST_COMPARE (pkey_get (keys[0]), 0);
for (int i = 1; i < key_count; ++i)
{
keys[i] = pkey_alloc (0, i);
if (keys[i] < 0)
FAIL_EXIT1 ("pkey_alloc (0, %d): %m", i);
/* pkey_alloc is supposed to change the current thread's access
rights for the new key. */
TEST_COMPARE (pkey_get (keys[i]), i);
}
/* Check that all the keys have the expected access rights for the
current thread. */
for (int i = 0; i < key_count; ++i)
TEST_COMPARE (pkey_get (keys[i]), i);
/* Allocate a test page for each key. */
for (int i = 0; i < key_count; ++i)
{
pages[i] = xmmap (NULL, pagesize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1);
TEST_COMPARE (pkey_mprotect ((void *) pages[i], pagesize,
PROT_READ | PROT_WRITE, keys[i]), 0);
}
/* Check that the initial thread does not have access to the new
keys. */
{
pthread_barrier_wait (&barrier);
struct thread_result *result = xpthread_join (delayed_thread);
for (int i = 0; i < key_count; ++i)
TEST_COMPARE (result->access_rights[i],
PKEY_DISABLE_ACCESS);
struct thread_result *result2 = xpthread_join (result->next_thread);
for (int i = 0; i < key_count; ++i)
TEST_COMPARE (result->access_rights[i],
PKEY_DISABLE_ACCESS);
free (result);
free (result2);
}
/* Check that the current thread access rights are inherited by new
threads. */
{
pthread_t get_thread = xpthread_create (NULL, get_thread_func, NULL);
struct thread_result *result = xpthread_join (get_thread);
for (int i = 0; i < key_count; ++i)
TEST_COMPARE (result->access_rights[i], i);
free (result);
}
for (int i = 0; i < key_count; ++i)
TEST_COMPARE (pkey_get (keys[i]), i);
/* Check that in a signal handler, there is no access. */
xsignal (SIGUSR1, &sigusr1_handler);
xraise (SIGUSR1);
xsignal (SIGUSR1, SIG_DFL);
TEST_COMPARE (sigusr1_handler_ran, 1);
/* The first key results in a writable page. */
TEST_VERIFY (check_page_access (0, false));
TEST_VERIFY (check_page_access (0, true));
/* The other keys do not. */
for (int i = 1; i < key_count; ++i)
{
if (test_verbose)
printf ("info: checking access for key %d, bits 0x%x\n",
i, pkey_get (keys[i]));
for (int j = 0; j < key_count; ++j)
TEST_COMPARE (pkey_get (keys[j]), j);
if (i & PKEY_DISABLE_ACCESS)
{
TEST_VERIFY (!check_page_access (i, false));
TEST_VERIFY (!check_page_access (i, true));
}
else
{
TEST_VERIFY (i & PKEY_DISABLE_WRITE);
TEST_VERIFY (check_page_access (i, false));
TEST_VERIFY (!check_page_access (i, true));
}
}
/* But if we set the current thread's access rights, we gain
access. */
for (int do_write = 0; do_write < 2; ++do_write)
for (int allowed_key = 0; allowed_key < key_count; ++allowed_key)
{
for (int i = 0; i < key_count; ++i)
if (i == allowed_key)
{
if (do_write)
TEST_COMPARE (pkey_set (keys[i], 0), 0);
else
TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_WRITE), 0);
}
else
TEST_COMPARE (pkey_set (keys[i], PKEY_DISABLE_ACCESS), 0);
if (test_verbose)
printf ("info: key %d is allowed access for %s\n",
allowed_key, do_write ? "writing" : "reading");
for (int i = 0; i < key_count; ++i)
if (i == allowed_key)
{
TEST_VERIFY (check_page_access (i, false));
TEST_VERIFY (check_page_access (i, true) == do_write);
}
else
{
TEST_VERIFY (!check_page_access (i, false));
TEST_VERIFY (!check_page_access (i, true));
}
}
/* Restore access to all keys, and launch a thread which should
inherit that access. */
for (int i = 0; i < key_count; ++i)
{
TEST_COMPARE (pkey_set (keys[i], 0), 0);
TEST_VERIFY (check_page_access (i, false));
TEST_VERIFY (check_page_access (i, true));
}
delayed_thread_check_access = false;
delayed_thread = xpthread_create
(NULL, delayed_thread_func, &delayed_thread_check_access);
TEST_COMPARE (pkey_free (keys[0]), 0);
/* Second pkey_free will fail because the key has already been
freed. */
TEST_COMPARE (pkey_free (keys[0]),-1);
TEST_COMPARE (errno, EINVAL);
for (int i = 1; i < key_count; ++i)
TEST_COMPARE (pkey_free (keys[i]), 0);
/* Check what happens to running threads which have access to
previously allocated protection keys. The implemented behavior
is somewhat dubious: Ideally, pkey_free should revoke access to
that key and pkey_alloc of the same (numeric) key should not
implicitly confer access to already-running threads, but this is
not what happens in practice. */
{
/* The limit is in place to avoid running indefinitely in case
there many keys available. */
int *keys_array = xcalloc (100000, sizeof (*keys_array));
int keys_allocated = 0;
while (keys_allocated < 100000)
{
int new_key = pkey_alloc (0, PKEY_DISABLE_WRITE);
if (new_key < 0)
{
/* No key reuse observed before running out of keys. */
TEST_COMPARE (errno, ENOSPC);
break;
}
for (int i = 0; i < key_count; ++i)
if (new_key == keys[i])
{
/* We allocated the key with disabled write access.
This should affect the protection state of the
existing page. */
TEST_VERIFY (check_page_access (i, false));
TEST_VERIFY (!check_page_access (i, true));
xpthread_barrier_wait (&barrier);
struct thread_result *result = xpthread_join (delayed_thread);
/* The thread which was launched before should still have
access to the key. */
TEST_COMPARE (result->access_rights[i], 0);
struct thread_result *result2
= xpthread_join (result->next_thread);
/* Same for a thread which is launched afterwards from
the old thread. */
TEST_COMPARE (result2->access_rights[i], 0);
free (result);
free (result2);
keys_array[keys_allocated++] = new_key;
goto after_key_search;
}
/* Save key for later deallocation. */
keys_array[keys_allocated++] = new_key;
}
after_key_search:
/* Deallocate the keys allocated for testing purposes. */
for (int j = 0; j < keys_allocated; ++j)
TEST_COMPARE (pkey_free (keys_array[j]), 0);
free (keys_array);
}
for (int i = 0; i < key_count; ++i)
xmunmap ((void *) pages[i], pagesize);
xpthread_barrier_destroy (&barrier);
return 0;
}
#include <support/test-driver.c>

View file

@ -0,0 +1,40 @@
/* Helper functions for manipulating memory protection keys.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#ifndef _ARCH_PKEY_H
#define _ARCH_PKEY_H
/* Return the value of the PKRU register. */
static inline unsigned int
pkey_read (void)
{
unsigned int result;
__asm__ volatile (".byte 0x0f, 0x01, 0xee"
: "=a" (result) : "c" (0) : "rdx");
return result;
}
/* Overwrite the PKRU register with VALUE. */
static inline void
pkey_write (unsigned int value)
{
__asm__ volatile (".byte 0x0f, 0x01, 0xef"
: : "a" (value), "c" (0), "d" (0));
}
#endif /* _ARCH_PKEY_H */

View file

@ -0,0 +1,33 @@
/* Reading the per-thread memory protection key, x86_64 version.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <arch-pkey.h>
#include <errno.h>
int
pkey_get (int key)
{
if (key < 0 || key > 15)
{
__set_errno (EINVAL);
return -1;
}
unsigned int pkru = pkey_read ();
return (pkru >> (2 * key)) & 3;
return 0;
}

View file

@ -0,0 +1,35 @@
/* Changing the per-thread memory protection key, x86_64 version.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <arch-pkey.h>
#include <errno.h>
int
pkey_set (int key, unsigned int rights)
{
if (key < 0 || key > 15 || rights > 3)
{
__set_errno (EINVAL);
return -1;
}
unsigned int mask = 3 << (2 * key);
unsigned int pkru = pkey_read ();
pkru = (pkru & ~mask) | (rights << (2 * key));
pkey_write (pkru);
return 0;
}

View file

@ -1879,6 +1879,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof64x F
GLIBC_2.27 strtof64x_l F

View file

@ -2122,6 +2122,11 @@ GLIBC_2.27 glob F
GLIBC_2.27 glob64 F
GLIBC_2.27 memfd_create F
GLIBC_2.27 mlock2 F
GLIBC_2.27 pkey_alloc F
GLIBC_2.27 pkey_free F
GLIBC_2.27 pkey_get F
GLIBC_2.27 pkey_mprotect F
GLIBC_2.27 pkey_set F
GLIBC_2.27 strfromf64x F
GLIBC_2.27 strtof64x F
GLIBC_2.27 strtof64x_l F