Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1592304 > unrolled thread
| Started by | Ricardo Neri <ricardo.neri-calderon@linux.intel.com> |
|---|---|
| First post | 2017-03-03 22:50 +0100 |
| Last post | 2017-03-03 22:50 +0100 |
| Articles | 7 — 2 participants |
Back to article view | Back to linux.kernel
[v5 00/20] x86: Enable User-Mode Instruction Prevention Ricardo Neri <ricardo.neri-calderon@linux.intel.com> - 2017-03-03 22:50 +0100
[v5 05/20] x86/insn-eval: Add utility functions to get register offsets Ricardo Neri <ricardo.neri-calderon@linux.intel.com> - 2017-03-03 22:50 +0100
[v5 16/20] x86: Add emulation code for UMIP instructions Ricardo Neri <ricardo.neri-calderon@linux.intel.com> - 2017-03-03 22:50 +0100
[v5 17/20] x86/umip: Force a page fault when unable to copy emulated result to user Ricardo Neri <ricardo.neri-calderon@linux.intel.com> - 2017-03-03 22:50 +0100
Re: [v5 17/20] x86/umip: Force a page fault when unable to copy emulated result to user Andy Lutomirski <luto@amacapital.net> - 2017-03-05 17:20 +0100
Re: [v5 17/20] x86/umip: Force a page fault when unable to copy emulated result to user Ricardo Neri <ricardo.neri-calderon@linux.intel.com> - 2017-03-07 01:30 +0100
[v5 09/20] x86/insn-eval: Add functions to get default operand and address sizes Ricardo Neri <ricardo.neri-calderon@linux.intel.com> - 2017-03-03 22:50 +0100
| From | Ricardo Neri <ricardo.neri-calderon@linux.intel.com> |
|---|---|
| Date | 2017-03-03 22:50 +0100 |
| Subject | [v5 00/20] x86: Enable User-Mode Instruction Prevention |
| Message-ID | <th3fj-79V-3@gated-at.bofh.it> |
This is v5 of this series. The four previous submissions can be found
here [1], here [2], here[3], and here [4]. This version addresses the
comments received in v4 plus improvements of the handling of emulation
in 64-bit builds. Please see details in the change log.
=== What is UMIP?
User-Mode Instruction Prevention (UMIP) is a security feature present in
new Intel Processors. If enabled, it prevents the execution of certain
instructions if the Current Privilege Level (CPL) is greater than 0. If
these instructions were executed while in CPL > 0, user space applications
could have access to system-wide settings such as the global and local
descriptor tables, the segment selectors to the current task state and the
local descriptor table.
These are the instructions covered by UMIP:
* SGDT - Store Global Descriptor Table
* SIDT - Store Interrupt Descriptor Table
* SLDT - Store Local Descriptor Table
* SMSW - Store Machine Status Word
* STR - Store Task Register
If any of these instructions is executed with CPL > 0, a general protection
exception is issued when UMIP is enabled.
=== How does it impact applications?
There is a caveat, however. Certain applications rely on some of these
instructions to function. An example of this are applications that use
WineHQ[6]. For instance, these applications rely on sidt returning a non-
accessible memory location[6]. During the discussions, it was proposed that
the fault could be relied to the user-space and perform the emulation in
user-mode. However, this would break existing applications until, for
instance, they update to a new WineHQ version. However, this approach
would require UMIP to be disabled by default. The consensus in this forum
is to always enable it.
This patchset initially treated tasks running in virtual-8086 mode as a
special case. However, I received clarification that DOSEMU[7] does not
support applications that use these instructions. It relies on WineHQ for
this [8]. Furthermore, the applications for which the concern was raised
run in protected mode [6].
Please note that UMIP is always enabled for both 64-bit and 32-bit Linux
builds. However, emulation of the UMIP-protected instructions is not done
for 64-bit processes. 64-bit user space applications will receive the
SIGSEGV signal when UMIP instructions causes a general protection fault.
=== How are UMIP-protected instructions emulated?
This version keeps UMIP enabled at all times and by default. If a general
protection fault caused by the instructions protected by UMIP is
detected, such fault will be fixed-up by returning dummy values as follows:
* SGDT and SIDT return hard-coded dummy values as the base of the global
descriptor and interrupt descriptor tables. These hard-coded values
correspond to memory addresses that are near the end of the kernel
memory map. This is also the case for virtual-8086 mode tasks. In all
my experiments in x86_32, the base of GDT and IDT was always a 4-byte
address, even for 16-bit operands. Thus, my emulation code does the
same. In all cases, the limit of the table is set to 0.
* STR and SLDT return 0 as the segment selector. This looks appropriate
since we are providing a dummy value as the base address of the global
descriptor table.
* SMSW returns the value with which the CR0 register is programmed in
head_32/64.S at boot time. This is, the following bits are enabled:
CR0.0 for Protection Enable, CR.1 for Monitor Coprocessor, CR.4 for
Extension Type, which will always be 1 in recent processors with UMIP;
CR.5 for Numeric Error, CR0.16 for Write Protect, CR0.18 for Alignment
Mask. As per the Intel 64 and IA-32 Architectures Software Developer's
Manual, SMSW returns a 16-bit results for memory operands. However, when
the operand is a register, the results can be up to CR0[63:0]. Since
the emulation code only kicks-in in x86_32, we return up to CR[31:0].
* The proposed emulation code is handles faults that happens in both
protected and virtual-8086 mode.
=== How is this series laid out?
++ Fix bugs in MPX address evaluator
I found very useful the code for Intel MPX (Memory Protection Extensions)
used to parse opcodes and the memory locations contained in the general
purpose registers when used as operands. I put some of this code in
a separate library file that both MPX and UMIP can access and avoid code
duplication. Before creating the new library, I fixed a couple of bugs
that I found in how MPX determines the address contained in the
instruction and operands.
++ Provide a new x86 instruction evaluating library
With bugs fixed, the MPX evaluating code is relocated in a new insn-eval.c
library. The basic functionality of this library is extended to obtain the
segment descriptor selected by either segment override prefixes or the
default segment by the involved registers in the calculation of the
effective address. It was also extended to obtain the default address and
operand sizes as well as the segment base address. Also, support to
process 16-bit address encodings. Armed with this arsenal, it is now
possible to determine the linear address onto which the emulated results
shall be copied.
This code supports Normal 32-bit and 64-bit (i.e., __USER32_CS and/or
__USER_CS) protected mode, virtual-8086 mode, 16-bit protected mode with
32-bit base address.
++ Emulate UMIP instructions
A new fixup_umip_exception functions inspect the instruction at the
instruction pointer. If it is an UMIP-protected instruction, it executes
the emulation code. This uses all the address-computing code of the
previous section.
++ Add self-tests
Lastly, self-tests are added to entry_from_v86.c to exercise the most
typical use cases of UMIP-protected instructions in a virtual-8086 mode.
++ Extensive tests
Extensive tests were performed to test all the combinations of ModRM,
SiB and displacements for 16-bit and 32-bit encodings for the ss, ds,
es and fs segments. Tests also include a 64-bit program that uses
segmentation via FS and GS. For this purpose, I temporarily, and not
as part of this patchset, enabled UMIP support for 64-bit process with
the intention to test the computations of linear addresses in 64-bit
mode, including the extra R8-R15 registers. Extensive test is also
implemented for virtual-8086 tasks. Code of these tests can be found here
[9] and here [10].
[1]. https://lwn.net/Articles/705877/
[2]. https://lkml.org/lkml/2016/12/23/265
[3]. https://lkml.org/lkml/2017/1/25/622
[4]. https://lkml.org/lkml/2017/2/23/40
[5]. https://www.winehq.org/
[6]. https://www.winehq.org/pipermail/wine-devel/2016-November/115320.html
[7]. http://www.dosemu.org/
[8]. http://marc.info/?l=linux-kernel&m=147876798717927&w=2
[9]. https://github.com/01org/luv-yocto/tree/rneri/umip/meta-luv/recipes-core/umip/files
[10]. https://github.com/01org/luv-yocto/commit/a72a7fe7d68693c0f4100ad86de6ecabde57334f#diff-3860c136a63add269bce4ea50222c248R1
Thanks and BR,
Ricardo
Changes since V4:
* Audited patches to use braces in all the branches of conditional.
statements, except those in which the conditional action only takes one
line.
* Implemented support in 64-builds for both 32-bit and 64-bit tasks in the
instruction evaluating library.
* Split segment selector function in the instruction evaluating library
into two functions to resolve the segment type by instruction override
or default and a separate function to actually read the segment selector.
* Fixed a bug when evaluating 32-bit effective addresses with 64-bit
kernels.
* Split patches further for for easier review.
* Use signed variables for computation of effective address.
* Fixed issue with a spurious static modifier in function insn_get_addr_ref
found by kbuild test bot.
* Removed comparison between true and fixup_umip_exception.
* Reworked check logic when identifying erroneous vs invalid values of the
SiB base and index.
Changes since V3:
* Limited emulation to 32-bit and 16-bit modes. For 64-bit mode, a general
protection fault is still issued when UMIP-protected instructions are
executed with CPL > 0.
* Expanded instruction-evaluating code to obtain segment descriptor along
with their attributes such as base address and default address and
operand sizes. Also, support for 16-bit encodings in protected mode was
implemented.
* When getting a segment descriptor, this include support to obtain those
of a local descriptor table.
* Now the instruction-evaluating code returns -EDOM when the value of
registers should not be used in calculating the effective address. The
value -EINVAL is left for errors.
* Incorporate the value of the segment base address in the computation of
linear addresses.
* Renamed new instruction evaluation library from insn-kernel.c to
insn-eval.c
* Exported functions insn_get_reg_offset_* to obtain the register offset
by ModRM r/m, SiB base and SiB index.
* Improved documentation of functions.
* Split patches further for easier review.
Changes since V2:
* Added new utility functions to decode the memory addresses contained in
registers when the 16-bit addressing encodings are used. This includes
code to obtain and compute memory addresses using segment selectors for
real-mode address translation.
* Added support to emulate UMIP-protected instructions for virtual-8086
tasks.
* Added self-tests for virtual-8086 mode that contains representative
use cases: address represented as a displacement, address in registers
and registers as operands.
* Instead of maintaining a static variable for the dummy base addresses
of the IDT and GDT, a hard-coded value is used.
* The emulated SMSW instructions now return the value with which the CR0
register is programmed in head_32/64.S This is: PE | MP | ET | NE | WP
| AM. For x86_64, PG is also enabled.
* The new file arch/x86/lib/insn-utils.c is now renamed as arch/x86/lib/
insn-kernel.c. It also has its own header. This helps keep in sync the
the kernel and objtool instruction decoders. Also, the new insn-kernel.c
contains utility functions that are only relevant in a kernel context.
* Removed printed warnings for errors that occur when decoding instructions
with invalid operands.
* Added more comments on fixes in the instruction-decoding MPX functions.
* Now user_64bit_mode(regs) is used instead of test_thread_flag(TIF_IA32)
to determine if the task is 32-bit or 64-bit.
* Found and fixed a bug in insn-decoder in which X86_MODRM_RM was
incorrectly used to obtain the mod part of the ModRM byte.
* Added more explanatory code in emulation and instruction decoding code.
This includes a comment regarding that copy_from_user could fail if there
exists a memory protection key in place.
* Tested code with CONFIG_X86_DECODER_SELFTEST=y and everything passes now.
* Prefixed get_reg_offset_rm with insn_ as this function is exposed
via a header file. For clarity, this function was added in a separate
patch.
Changes since V1:
* Virtual-8086 mode tasks are not treated in a special manner. All code
for this purpose was removed.
* Instead of attempting to disable UMIP during a context switch or when
entering virtual-8086 mode, UMIP remains enabled all the time. General
protection faults that occur are fixed-up by returning dummy values as
detailed above.
* Removed umip= kernel parameter in favor of using clearcpuid=514 to
disable UMIP.
* Removed selftests designed to detect the absence of SIGSEGV signals when
running in virtual-8086 mode.
* Reused code from MPX to decode instructions operands. For this purpose
code was put in a common location.
* Fixed two bugs in MPX code that decodes operands.
Thanks and BR,
Ricardo
Ricardo Neri (20):
x86/mpx: Use signed variables to compute effective addresses
x86/mpx: Do not use SIB index if index points to R/ESP
x86/mpx: Do not use R/EBP as base in the SIB byte with Mod = 0
x86/mpx, x86/insn: Relocate insn util functions to a new insn-kernel
x86/insn-eval: Add utility functions to get register offsets
x86/insn-eval: Add utility functions to get segment selector
x86/insn-eval: Add utility function to get segment descriptor
x86/insn-eval: Add utility function to get segment descriptor base
address
x86/insn-eval: Add functions to get default operand and address sizes
x86/insn-eval: Do not use R/EBP as base if mod in ModRM is zero
insn/eval: Incorporate segment base in address computation
x86/insn: Support both signed 32-bit and 64-bit effective addresses
x86/insn-eval: Add support to resolve 16-bit addressing encodings
x86/insn-eval: Add wrapper function for 16-bit and 32-bit address
encodings
x86/cpufeature: Add User-Mode Instruction Prevention definitions
x86: Add emulation code for UMIP instructions
x86/umip: Force a page fault when unable to copy emulated result to
user
x86/traps: Fixup general protection faults caused by UMIP
x86: Enable User-Mode Instruction Prevention
selftests/x86: Add tests for User-Mode Instruction Prevention
arch/x86/Kconfig | 10 +
arch/x86/include/asm/cpufeatures.h | 1 +
arch/x86/include/asm/disabled-features.h | 8 +-
arch/x86/include/asm/insn-eval.h | 23 +
arch/x86/include/asm/umip.h | 15 +
arch/x86/include/uapi/asm/processor-flags.h | 2 +
arch/x86/kernel/Makefile | 1 +
arch/x86/kernel/cpu/common.c | 16 +-
arch/x86/kernel/traps.c | 4 +
arch/x86/kernel/umip.c | 305 ++++++++++
arch/x86/lib/Makefile | 2 +-
arch/x86/lib/insn-eval.c | 832 ++++++++++++++++++++++++++
arch/x86/mm/mpx.c | 120 +---
tools/testing/selftests/x86/entry_from_vm86.c | 39 +-
14 files changed, 1256 insertions(+), 122 deletions(-)
create mode 100644 arch/x86/include/asm/insn-eval.h
create mode 100644 arch/x86/include/asm/umip.h
create mode 100644 arch/x86/kernel/umip.c
create mode 100644 arch/x86/lib/insn-eval.c
--
2.9.3
[toc] | [next] | [standalone]
| From | Ricardo Neri <ricardo.neri-calderon@linux.intel.com> |
|---|---|
| Date | 2017-03-03 22:50 +0100 |
| Subject | [v5 05/20] x86/insn-eval: Add utility functions to get register offsets |
| Message-ID | <th3fk-79V-45@gated-at.bofh.it> |
| In reply to | #1592304 |
The function insn_get_reg_offset takes as argument an enumeration that
indicates the type of offset that is returned: the R/M part of the ModRM
byte, the index of the SIB byte or the base of the SIB byte. Callers of
this function would need the definition of such enumeration. This is not
needed. Instead, helper functions can be defined for this purpose can be
added. These functions are useful in cases when, for instance, the caller
needs to decide whether the operand is a register or a memory location by
looking at the mod part of the ModRM byte.
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Adam Buchbinder <adam.buchbinder@gmail.com>
Cc: Colin Ian King <colin.king@canonical.com>
Cc: Lorenzo Stoakes <lstoakes@gmail.com>
Cc: Qiaowei Ren <qiaowei.ren@intel.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Thomas Garnier <thgarnie@google.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Borislav Petkov <bp@suse.de>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Ravi V. Shankar <ravi.v.shankar@intel.com>
Cc: x86@kernel.org
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
arch/x86/include/asm/insn-eval.h | 3 +++
arch/x86/lib/insn-eval.c | 51 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 54 insertions(+)
diff --git a/arch/x86/include/asm/insn-eval.h b/arch/x86/include/asm/insn-eval.h
index 5cab1b1..754211b 100644
--- a/arch/x86/include/asm/insn-eval.h
+++ b/arch/x86/include/asm/insn-eval.h
@@ -12,5 +12,8 @@
#include <asm/ptrace.h>
void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs);
+int insn_get_reg_offset_modrm_rm(struct insn *insn, struct pt_regs *regs);
+int insn_get_reg_offset_sib_base(struct insn *insn, struct pt_regs *regs);
+int insn_get_reg_offset_sib_base(struct insn *insn, struct pt_regs *regs);
#endif /* _ASM_X86_INSN_EVAL_H */
diff --git a/arch/x86/lib/insn-eval.c b/arch/x86/lib/insn-eval.c
index 23cf010..78df1c9 100644
--- a/arch/x86/lib/insn-eval.c
+++ b/arch/x86/lib/insn-eval.c
@@ -98,6 +98,57 @@ static int get_reg_offset(struct insn *insn, struct pt_regs *regs,
return regoff[regno];
}
+/**
+ * insn_get_reg_offset_modrm_rm - Obtain register in r/m part of ModRM byte
+ * @insn: Instruction structure containing the ModRM byte
+ * @regs: Set of registers indicated by the ModRM byte
+ *
+ * Obtain the register indicated by the r/m part of the ModRM byte. The
+ * register is obtained as an offset from the base of pt_regs. In specific
+ * cases, the returned value can be -EDOM to indicate that the particular value
+ * of ModRM does not refer to a register.
+ *
+ * Return: Register indicated by r/m, as an offset within struct pt_regs
+ */
+int insn_get_reg_offset_modrm_rm(struct insn *insn, struct pt_regs *regs)
+{
+ return get_reg_offset(insn, regs, REG_TYPE_RM);
+}
+
+/**
+ * insn_get_reg_offset_sib_base - Obtain register in base part of SiB byte
+ * @insn: Instruction structure containing the SiB byte
+ * @regs: Set of registers indicated by the SiB byte
+ *
+ * Obtain the register indicated by the base part of the SiB byte. The
+ * register is obtained as an offset from the base of pt_regs. In specific
+ * cases, the returned value can be -EDOM to indicate that the particular value
+ * of SiB does not refer to a register.
+ *
+ * Return: Register indicated by SiB's base, as an offset within struct pt_regs
+ */
+int insn_get_reg_offset_sib_base(struct insn *insn, struct pt_regs *regs)
+{
+ return get_reg_offset(insn, regs, REG_TYPE_BASE);
+}
+
+/**
+ * insn_get_reg_offset_sib_index - Obtain register in index part of SiB byte
+ * @insn: Instruction structure containing the SiB byte
+ * @regs: Set of registers indicated by the SiB byte
+ *
+ * Obtain the register indicated by the index part of the SiB byte. The
+ * register is obtained as an offset from the index of pt_regs. In specific
+ * cases, the returned value can be -EDOM to indicate that the particular value
+ * of SiB does not refer to a register.
+ *
+ * Return: Register indicated by SiB's base, as an offset within struct pt_regs
+ */
+int insn_get_reg_offset_sib_index(struct insn *insn, struct pt_regs *regs)
+{
+ return get_reg_offset(insn, regs, REG_TYPE_INDEX);
+}
+
/*
* return the address being referenced be instruction
* for rm=3 returning the content of the rm reg
--
2.9.3
[toc] | [prev] | [next] | [standalone]
| From | Ricardo Neri <ricardo.neri-calderon@linux.intel.com> |
|---|---|
| Date | 2017-03-03 22:50 +0100 |
| Subject | [v5 16/20] x86: Add emulation code for UMIP instructions |
| Message-ID | <th3fl-79V-49@gated-at.bofh.it> |
| In reply to | #1592304 |
The feature User-Mode Instruction Prevention present in recent Intel
processor prevents a group of instructions from being executed with
CPL > 0. Otherwise, a general protection fault is issued.
Rather than relaying this fault to the user space (in the form of a SIGSEGV
signal), the instructions protected by UMIP can be emulated to provide
dummy results. This allows to conserve the current kernel behavior and not
reveal the system resources that UMIP intends to protect (the global
descriptor and interrupt descriptor tables, the segment selectors of the
local descriptor table and the task state and the machine status word).
This emulation is needed because certain applications (e.g., WineHQ) rely
on this subset of instructions to function.
The instructions protected by UMIP can be split in two groups. Those who
return a kernel memory address (sgdt and sidt) and those who return a
value (sldt, str and smsw).
For the instructions that return a kernel memory address, applications
such as WineHQ rely on the result being located in the kernel memory space.
The result is emulated as a hard-coded value that, lies close to the top
of the kernel memory. The limit for the GDT and the IDT are set to zero.
The instructions sldt and str return a segment selector relative to the
base address of the global descriptor table. Since the actual address of
such table is not revealed, it makes sense to emulate the result as zero.
The instruction smsw is emulated to return the value that the register CR0
has at boot time as set in the head_32.
Care is taken to appropriately emulate the results when segmentation is
used. This is, rather than relying on USER_DS and USER_CS, the function
insn_get_addr_ref inspects the segment descriptor pointed by the registers
in pt_regs. This ensures that we correctly obtain the segment base address
and the address and operand sizes even if the user space application uses
local descriptor table.
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Borislav Petkov <bp@suse.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Chen Yucong <slaoub@gmail.com>
Cc: Chris Metcalf <cmetcalf@mellanox.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Fenghua Yu <fenghua.yu@intel.com>
Cc: Huang Rui <ray.huang@amd.com>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Michael S. Tsirkin <mst@redhat.com>
Cc: Paul Gortmaker <paul.gortmaker@windriver.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ravi V. Shankar <ravi.v.shankar@intel.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Tony Luck <tony.luck@intel.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Liang Z. Li <liang.z.li@intel.com>
Cc: Alexandre Julliard <julliard@winehq.org>
Cc: Stas Sergeev <stsp@list.ru>
Cc: x86@kernel.org
Cc: linux-msdos@vger.kernel.org
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
arch/x86/include/asm/umip.h | 15 +++
arch/x86/kernel/Makefile | 1 +
arch/x86/kernel/umip.c | 264 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 280 insertions(+)
create mode 100644 arch/x86/include/asm/umip.h
create mode 100644 arch/x86/kernel/umip.c
diff --git a/arch/x86/include/asm/umip.h b/arch/x86/include/asm/umip.h
new file mode 100644
index 0000000..077b236
--- /dev/null
+++ b/arch/x86/include/asm/umip.h
@@ -0,0 +1,15 @@
+#ifndef _ASM_X86_UMIP_H
+#define _ASM_X86_UMIP_H
+
+#include <linux/types.h>
+#include <asm/ptrace.h>
+
+#ifdef CONFIG_X86_INTEL_UMIP
+bool fixup_umip_exception(struct pt_regs *regs);
+#else
+static inline bool fixup_umip_exception(struct pt_regs *regs)
+{
+ return false;
+}
+#endif /* CONFIG_X86_INTEL_UMIP */
+#endif /* _ASM_X86_UMIP_H */
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index 84c0059..0ded7b1 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -122,6 +122,7 @@ obj-$(CONFIG_EFI) += sysfb_efi.o
obj-$(CONFIG_PERF_EVENTS) += perf_regs.o
obj-$(CONFIG_TRACING) += tracepoint.o
obj-$(CONFIG_SCHED_MC_PRIO) += itmt.o
+obj-$(CONFIG_X86_INTEL_UMIP) += umip.o
ifdef CONFIG_FRAME_POINTER
obj-y += unwind_frame.o
diff --git a/arch/x86/kernel/umip.c b/arch/x86/kernel/umip.c
new file mode 100644
index 0000000..e932f40
--- /dev/null
+++ b/arch/x86/kernel/umip.c
@@ -0,0 +1,264 @@
+/*
+ * umip.c Emulation for instruction protected by the Intel User-Mode
+ * Instruction Prevention. The instructions are:
+ * sgdt
+ * sldt
+ * sidt
+ * str
+ * smsw
+ *
+ * Copyright (c) 2017, Intel Corporation.
+ * Ricardo Neri <ricardo.neri@linux.intel.com>
+ */
+
+#include <linux/uaccess.h>
+#include <asm/umip.h>
+#include <asm/traps.h>
+#include <asm/insn.h>
+#include <asm/insn-eval.h>
+#include <linux/ratelimit.h>
+
+/*
+ * == Base addresses of GDT and IDT
+ * Some applications to function rely finding the global descriptor table (GDT)
+ * and the interrupt descriptor table (IDT) in kernel memory.
+ * For x86_32, the selected values do not match any particular hole, but it
+ * suffices to provide a memory location within kernel memory.
+ *
+ * == CRO flags for SMSW
+ * Use the flags given when booting, as found in head_32.S
+ */
+
+#define CR0_STATE (X86_CR0_PE | X86_CR0_MP | X86_CR0_ET | X86_CR0_NE | \
+ X86_CR0_WP | X86_CR0_AM)
+#define UMIP_DUMMY_GDT_BASE 0xfffe0000
+#define UMIP_DUMMY_IDT_BASE 0xffff0000
+
+/*
+ * Definitions for x86 page fault error code bits. Only a simple
+ * pagefault during a write in user context is supported.
+ */
+#define UMIP_PF_USER BIT(2)
+#define UMIP_PF_WRITE BIT(1)
+
+enum umip_insn {
+ UMIP_SGDT = 0, /* opcode 0f 01 ModR/M reg 0 */
+ UMIP_SIDT, /* opcode 0f 01 ModR/M reg 1 */
+ UMIP_SLDT, /* opcode 0f 00 ModR/M reg 0 */
+ UMIP_SMSW, /* opcode 0f 01 ModR/M reg 4 */
+ UMIP_STR, /* opcode 0f 00 ModR/M reg 1 */
+};
+
+/**
+ * __identify_insn - Identify a UMIP-protected instruction
+ * @insn: Instruction structure with opcode and ModRM byte.
+ *
+ * From the instruction opcode and the reg part of the ModRM byte, identify,
+ * if any, a UMIP-protected instruction.
+ *
+ * Return: an enumeration of a UMIP-protected instruction; -EINVAL on failure.
+ */
+static int __identify_insn(struct insn *insn)
+{
+ /* By getting modrm we also get the opcode. */
+ insn_get_modrm(insn);
+
+ /* All the instructions of interest start with 0x0f. */
+ if (insn->opcode.bytes[0] != 0xf)
+ return -EINVAL;
+
+ if (insn->opcode.bytes[1] == 0x1) {
+ switch (X86_MODRM_REG(insn->modrm.value)) {
+ case 0:
+ return UMIP_SGDT;
+ case 1:
+ return UMIP_SIDT;
+ case 4:
+ return UMIP_SMSW;
+ default:
+ return -EINVAL;
+ }
+ } else if (insn->opcode.bytes[1] == 0x0) {
+ if (X86_MODRM_REG(insn->modrm.value) == 0)
+ return UMIP_SLDT;
+ else if (X86_MODRM_REG(insn->modrm.value) == 1)
+ return UMIP_STR;
+ else
+ return -EINVAL;
+ } else {
+ return -EINVAL;
+ }
+}
+
+/**
+ * __emulate_umip_insn - Emulate UMIP instructions with dummy values
+ * @insn: Instruction structure with ModRM byte
+ * @umip_inst: Instruction to emulate
+ * @data: Buffer onto which the dummy values will be copied
+ * @data_size: Size of the emulated result
+ *
+ * Emulate an instruction protected by UMIP. The result of the emulation
+ * is saved in the provided buffer. The size of the results depends on both
+ * the instruction and type of operand (register vs memory address). Thus,
+ * the size of the result needs to be updated.
+ *
+ * Result: 0 if success, -EINVAL on failure to emulate
+ */
+static int __emulate_umip_insn(struct insn *insn, enum umip_insn umip_inst,
+ unsigned char *data, int *data_size)
+{
+ unsigned long dummy_base_addr;
+ unsigned short dummy_limit = 0;
+ unsigned int dummy_value = 0;
+
+ switch (umip_inst) {
+ /*
+ * These two instructions return the base address and limit of the
+ * global and interrupt descriptor table. The base address can be
+ * 24-bit, 32-bit or 64-bit. Limit is always 16-bit. If the operand
+ * size is 16-bit the returned value of the base address is supposed
+ * to be a zero-extended 24-byte number. However, it seems that a
+ * 32-byte number is always returned in legacy protected mode
+ * irrespective of the operand size.
+ */
+ case UMIP_SGDT:
+ /* fall through */
+ case UMIP_SIDT:
+ if (umip_inst == UMIP_SGDT)
+ dummy_base_addr = UMIP_DUMMY_GDT_BASE;
+ else
+ dummy_base_addr = UMIP_DUMMY_IDT_BASE;
+ if (X86_MODRM_MOD(insn->modrm.value) == 3) {
+ /* SGDT and SIDT do not take register as argument. */
+ return -EINVAL;
+ }
+
+ memcpy(data + 2, &dummy_base_addr, sizeof(dummy_base_addr));
+ memcpy(data, &dummy_limit, sizeof(dummy_limit));
+ *data_size = sizeof(dummy_base_addr) + sizeof(dummy_limit);
+ break;
+ case UMIP_SMSW:
+ /*
+ * Even though CR0_STATE contain 4 bytes, the number
+ * of bytes to be copied in the result buffer is determined
+ * by whether the operand is a register or a memory location.
+ */
+ dummy_value = CR0_STATE;
+ /*
+ * These two instructions return a 16-bit value. We return
+ * all zeros. This is equivalent to a null descriptor for
+ * str and sldt.
+ */
+ /* fall through */
+ case UMIP_SLDT:
+ /* fall through */
+ case UMIP_STR:
+ /* if operand is a register, it is zero-extended */
+ if (X86_MODRM_MOD(insn->modrm.value) == 3) {
+ memset(data, 0, insn->opnd_bytes);
+ *data_size = insn->opnd_bytes;
+ /* if not, only the two least significant bytes are copied */
+ } else {
+ *data_size = 2;
+ }
+ memcpy(data, &dummy_value, sizeof(dummy_value));
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * fixup_umip_exception - Fixup #GP faults caused by UMIP
+ * @regs: Registers as saved when entering the #GP trap
+ *
+ * The instructions sgdt, sidt, str, smsw, sldt cause a general protection
+ * fault if with CPL > 0 (i.e., from user space). This function can be
+ * used to emulate the results of the aforementioned instructions with
+ * dummy values. Results are copied to user-space memory as indicated by
+ * the instruction pointed by EIP using the registers indicated in the
+ * instruction operands. This function also takes care of determining
+ * the address to which the results must be copied.
+ */
+bool fixup_umip_exception(struct pt_regs *regs)
+{
+ struct insn insn;
+ unsigned char buf[MAX_INSN_SIZE];
+ /* 10 bytes is the maximum size of the result of UMIP instructions */
+ unsigned char dummy_data[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ unsigned long seg_base;
+ int not_copied, nr_copied, reg_offset, dummy_data_size;
+ void __user *uaddr;
+ unsigned long *reg_addr;
+ enum umip_insn umip_inst;
+
+ /*
+ * Use the segment base in case user space used a different code
+ * segment, either in protected (e.g., from an LDT) or virtual-8086
+ * modes. In most of the cases seg_base will be zero as in USER_CS.
+ */
+ seg_base = insn_get_seg_base(regs, &insn, offsetof(struct pt_regs, ip),
+ true);
+ not_copied = copy_from_user(buf, (void __user *)(seg_base + regs->ip),
+ sizeof(buf));
+ nr_copied = sizeof(buf) - not_copied;
+ /*
+ * The copy_from_user above could have failed if user code is protected
+ * by a memory protection key. Give up on emulation in such a case.
+ * Should we issue a page fault?
+ */
+ if (!nr_copied)
+ return false;
+
+ insn_init(&insn, buf, nr_copied, 0);
+
+ /*
+ * Override the default operand and address sizes to what is specified
+ * in the code segment descriptor. The instruction decoder only sets
+ * the address size it to either 4 or 8 address bytes and does nothing
+ * for the operand bytes. This OK for most of the cases, but we could
+ * have special cases where, for instance, a 16-bit code segment
+ * descriptor is used.
+ * If there are overrides, the instruction decoder correctly updates
+ * these values, even for 16-bit defaults.
+ */
+ insn.addr_bytes = insn_get_seg_default_address_bytes(regs);
+ insn.opnd_bytes = insn_get_seg_default_operand_bytes(regs);
+
+ if (!insn.addr_bytes || !insn.opnd_bytes)
+ return false;
+
+#ifdef CONFIG_X86_64
+ if (user_64bit_mode(regs))
+ return false;
+#endif
+
+ insn_get_length(&insn);
+ if (nr_copied < insn.length)
+ return false;
+
+ umip_inst = __identify_insn(&insn);
+ /* Check if we found an instruction protected by UMIP */
+ if (umip_inst < 0)
+ return false;
+
+ if (__emulate_umip_insn(&insn, umip_inst, dummy_data, &dummy_data_size))
+ return false;
+
+ /* If operand is a register, write directly to it */
+ if (X86_MODRM_MOD(insn.modrm.value) == 3) {
+ reg_offset = insn_get_reg_offset_modrm_rm(&insn, regs);
+ reg_addr = (unsigned long *)((unsigned long)regs + reg_offset);
+ memcpy(reg_addr, dummy_data, dummy_data_size);
+ } else {
+ uaddr = insn_get_addr_ref(&insn, regs);
+ nr_copied = copy_to_user(uaddr, dummy_data, dummy_data_size);
+ if (nr_copied > 0)
+ return false;
+ }
+
+ /* increase IP to let the program keep going */
+ regs->ip += insn.length;
+ return true;
+}
--
2.9.3
[toc] | [prev] | [next] | [standalone]
| From | Ricardo Neri <ricardo.neri-calderon@linux.intel.com> |
|---|---|
| Date | 2017-03-03 22:50 +0100 |
| Subject | [v5 17/20] x86/umip: Force a page fault when unable to copy emulated result to user |
| Message-ID | <th3fl-79V-47@gated-at.bofh.it> |
| In reply to | #1592304 |
fixup_umip_exception will be called from do_general_protection. If the
former returns false, the latter will issue a SIGSEGV with SEND_SIG_PRIV.
However, when emulation is successful but the emulated result cannot be
copied to user space memory, it is more accurate to issue a SIGSEGV with
SEGV_MAPERR with the offending address. A new function is inspired in
force_sig_info_fault is introduced to model the page fault.
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
arch/x86/kernel/umip.c | 45 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 43 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kernel/umip.c b/arch/x86/kernel/umip.c
index e932f40..5b6a7cf 100644
--- a/arch/x86/kernel/umip.c
+++ b/arch/x86/kernel/umip.c
@@ -170,6 +170,41 @@ static int __emulate_umip_insn(struct insn *insn, enum umip_insn umip_inst,
}
/**
+ * __force_sig_info_umip_fault - Force a SIGSEGV with SEGV_MAPERR
+ * @address: Address that caused the signal
+ * @regs: Register set containing the instruction pointer
+ *
+ * Force a SIGSEGV signal with SEGV_MAPERR as the error code. This function is
+ * intended to be used to provide a segmentation fault when the result of the
+ * UMIP emulation could not be copied to the user space memory.
+ *
+ * Return: none
+ */
+static void __force_sig_info_umip_fault(void __user *address,
+ struct pt_regs *regs)
+{
+ siginfo_t info;
+ struct task_struct *tsk = current;
+
+ if (show_unhandled_signals && unhandled_signal(tsk, SIGSEGV)) {
+ printk_ratelimited("%s[%d] umip emulation segfault ip:%lx sp:%lx error:%lx in %lx\n",
+ tsk->comm, task_pid_nr(tsk), regs->ip,
+ regs->sp, UMIP_PF_USER | UMIP_PF_WRITE,
+ regs->ip);
+ }
+
+ tsk->thread.cr2 = (unsigned long)address;
+ tsk->thread.error_code = UMIP_PF_USER | UMIP_PF_WRITE;
+ tsk->thread.trap_nr = X86_TRAP_PF;
+
+ info.si_signo = SIGSEGV;
+ info.si_errno = 0;
+ info.si_code = SEGV_MAPERR;
+ info.si_addr = address;
+ force_sig_info(SIGSEGV, &info, tsk);
+}
+
+/**
* fixup_umip_exception - Fixup #GP faults caused by UMIP
* @regs: Registers as saved when entering the #GP trap
*
@@ -254,8 +289,14 @@ bool fixup_umip_exception(struct pt_regs *regs)
} else {
uaddr = insn_get_addr_ref(&insn, regs);
nr_copied = copy_to_user(uaddr, dummy_data, dummy_data_size);
- if (nr_copied > 0)
- return false;
+ if (nr_copied > 0) {
+ /*
+ * If copy fails, send a signal and tell caller that
+ * fault was fixed up
+ */
+ __force_sig_info_umip_fault(uaddr, regs);
+ return true;
+ }
}
/* increase IP to let the program keep going */
--
2.9.3
[toc] | [prev] | [next] | [standalone]
| From | Andy Lutomirski <luto@amacapital.net> |
|---|---|
| Date | 2017-03-05 17:20 +0100 |
| Subject | Re: [v5 17/20] x86/umip: Force a page fault when unable to copy emulated result to user |
| Message-ID | <thH33-2Ka-1@gated-at.bofh.it> |
| In reply to | #1592307 |
On Fri, Mar 3, 2017 at 1:41 PM, Ricardo Neri
<ricardo.neri-calderon@linux.intel.com> wrote:
> fixup_umip_exception will be called from do_general_protection. If the
> former returns false, the latter will issue a SIGSEGV with SEND_SIG_PRIV.
> However, when emulation is successful but the emulated result cannot be
> copied to user space memory, it is more accurate to issue a SIGSEGV with
> SEGV_MAPERR with the offending address. A new function is inspired in
> force_sig_info_fault is introduced to model the page fault.
>
> Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
> ---
> arch/x86/kernel/umip.c | 45 +++++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 43 insertions(+), 2 deletions(-)
>
> diff --git a/arch/x86/kernel/umip.c b/arch/x86/kernel/umip.c
> index e932f40..5b6a7cf 100644
> --- a/arch/x86/kernel/umip.c
> +++ b/arch/x86/kernel/umip.c
> @@ -170,6 +170,41 @@ static int __emulate_umip_insn(struct insn *insn, enum umip_insn umip_inst,
> }
>
> /**
> + * __force_sig_info_umip_fault - Force a SIGSEGV with SEGV_MAPERR
> + * @address: Address that caused the signal
> + * @regs: Register set containing the instruction pointer
> + *
> + * Force a SIGSEGV signal with SEGV_MAPERR as the error code. This function is
> + * intended to be used to provide a segmentation fault when the result of the
> + * UMIP emulation could not be copied to the user space memory.
> + *
> + * Return: none
> + */
> +static void __force_sig_info_umip_fault(void __user *address,
> + struct pt_regs *regs)
> +{
> + siginfo_t info;
> + struct task_struct *tsk = current;
> +
> + if (show_unhandled_signals && unhandled_signal(tsk, SIGSEGV)) {
> + printk_ratelimited("%s[%d] umip emulation segfault ip:%lx sp:%lx error:%lx in %lx\n",
> + tsk->comm, task_pid_nr(tsk), regs->ip,
> + regs->sp, UMIP_PF_USER | UMIP_PF_WRITE,
> + regs->ip);
> + }
> +
> + tsk->thread.cr2 = (unsigned long)address;
> + tsk->thread.error_code = UMIP_PF_USER | UMIP_PF_WRITE;
Please just move enum x86_pf_error_code into a header and rename the
fields X86_PF_USER, etc rather than duplicating it.
--Andy
[toc] | [prev] | [next] | [standalone]
| From | Ricardo Neri <ricardo.neri-calderon@linux.intel.com> |
|---|---|
| Date | 2017-03-07 01:30 +0100 |
| Subject | Re: [v5 17/20] x86/umip: Force a page fault when unable to copy emulated result to user |
| Message-ID | <tibaN-7DV-5@gated-at.bofh.it> |
| In reply to | #1592787 |
On Sun, 2017-03-05 at 08:18 -0800, Andy Lutomirski wrote:
> > + */
> > +static void __force_sig_info_umip_fault(void __user *address,
> > + struct pt_regs *regs)
> > +{
> > + siginfo_t info;
> > + struct task_struct *tsk = current;
> > +
> > + if (show_unhandled_signals && unhandled_signal(tsk,
> SIGSEGV)) {
> > + printk_ratelimited("%s[%d] umip emulation segfault
> ip:%lx sp:%lx error:%lx in %lx\n",
> > + tsk->comm, task_pid_nr(tsk),
> regs->ip,
> > + regs->sp, UMIP_PF_USER |
> UMIP_PF_WRITE,
> > + regs->ip);
> > + }
> > +
> > + tsk->thread.cr2 = (unsigned long)address;
> > + tsk->thread.error_code = UMIP_PF_USER | UMIP_PF_WRITE;
>
> Please just move enum x86_pf_error_code into a header and rename the
> fields X86_PF_USER, etc rather than duplicating it.
Thanks again for your feedback! I will do this.
[toc] | [prev] | [next] | [standalone]
| From | Ricardo Neri <ricardo.neri-calderon@linux.intel.com> |
|---|---|
| Date | 2017-03-03 22:50 +0100 |
| Subject | [v5 09/20] x86/insn-eval: Add functions to get default operand and address sizes |
| Message-ID | <th3fl-79V-51@gated-at.bofh.it> |
| In reply to | #1592304 |
These functions read the default values of the address and operand sizes
as specified in the segment descriptor. This information is determined
from the D and L bits. Hence, it can be used for both IA-32e 64-bit and
32-bit legacy modes. For virtual-8086 mode, the default address and
operand sizes are always 2 bytes.
The D bit is only meaningful for code segments. Thus, these functions
always use the code segment selector contained in regs.
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Adam Buchbinder <adam.buchbinder@gmail.com>
Cc: Colin Ian King <colin.king@canonical.com>
Cc: Lorenzo Stoakes <lstoakes@gmail.com>
Cc: Qiaowei Ren <qiaowei.ren@intel.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Thomas Garnier <thgarnie@google.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Borislav Petkov <bp@suse.de>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Ravi V. Shankar <ravi.v.shankar@intel.com>
Cc: x86@kernel.org
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
arch/x86/include/asm/insn-eval.h | 2 +
arch/x86/lib/insn-eval.c | 80 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 82 insertions(+)
diff --git a/arch/x86/include/asm/insn-eval.h b/arch/x86/include/asm/insn-eval.h
index b201742..a0d81fc 100644
--- a/arch/x86/include/asm/insn-eval.h
+++ b/arch/x86/include/asm/insn-eval.h
@@ -15,6 +15,8 @@ void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs);
int insn_get_reg_offset_modrm_rm(struct insn *insn, struct pt_regs *regs);
int insn_get_reg_offset_sib_base(struct insn *insn, struct pt_regs *regs);
int insn_get_reg_offset_sib_base(struct insn *insn, struct pt_regs *regs);
+unsigned char insn_get_seg_default_address_bytes(struct pt_regs *regs);
+unsigned char insn_get_seg_default_operand_bytes(struct pt_regs *regs);
unsigned long insn_get_seg_base(struct pt_regs *regs, struct insn *insn,
int regoff, bool use_default_seg);
diff --git a/arch/x86/lib/insn-eval.c b/arch/x86/lib/insn-eval.c
index 383ca83..cda6c71 100644
--- a/arch/x86/lib/insn-eval.c
+++ b/arch/x86/lib/insn-eval.c
@@ -421,6 +421,86 @@ unsigned long insn_get_seg_base(struct pt_regs *regs, struct insn *insn,
}
/**
+ * insn_get_seg_default_address_bytes - Obtain default address size of segment
+ * @regs: Set of registers containing the segment selector
+ *
+ * Obtain the default address size as indicated in the segment descriptor
+ * selected in regs' code segment selector. In protected mode, the default
+ * address is determined by inspecting the L and D bits of the segment
+ * descriptor. In virtual-8086 mode, the default is always two bytes.
+ *
+ * Return: Default address size of segment
+ */
+unsigned char insn_get_seg_default_address_bytes(struct pt_regs *regs)
+{
+ struct desc_struct *desc;
+ unsigned short seg;
+ int ret;
+
+ if (v8086_mode(regs))
+ return 2;
+
+ seg = (unsigned short)regs->cs;
+
+ ret = get_desc(seg, &desc);
+ if (ret)
+ return 0;
+
+ switch ((desc->l << 1) | desc->d) {
+ case 0: /* Legacy mode. 16-bit addresses. CS.L=0, CS.D=0 */
+ return 2;
+ case 1: /* Legacy mode. 32-bit addresses. CS.L=0, CS.D=1 */
+ return 4;
+ case 2: /* IA-32e 64-bit mode. 64-bit addresses. CS.L=1, CS.D=0 */
+ return 8;
+ case 3: /* Invalid setting. CS.L=1, CS.D=1 */
+ /* fall through */
+ default:
+ return 0;
+ }
+}
+
+/**
+ * insn_get_seg_default_operand_bytes - Obtain default operand size of segment
+ * @regs: Set of registers containing the segment selector
+ *
+ * Obtain the default operand size as indicated in the segment descriptor
+ * selected in regs' code segment selector. In protected mode, the default
+ * operand size is determined by inspecting the L and D bits of the segment
+ * descriptor. In virtual-8086 mode, the default is always two bytes.
+ *
+ * Return: Default operand size of segment
+ */
+unsigned char insn_get_seg_default_operand_bytes(struct pt_regs *regs)
+{
+ struct desc_struct *desc;
+ unsigned short seg;
+ int ret;
+
+ if (v8086_mode(regs))
+ return 2;
+
+ seg = (unsigned short)regs->cs;
+
+ ret = get_desc(seg, &desc);
+ if (ret)
+ return 0;
+
+ switch ((desc->l << 1) | desc->d) {
+ case 0: /* Legacy mode. 16-bit or 8-bit operands CS.L=0, CS.D=0 */
+ return 2;
+ case 1: /* Legacy mode. 32- or 8 bit operands CS.L=0, CS.D=1 */
+ /* fall through */
+ case 2: /* IA-32e 64-bit mode. 32- or 8-bit opnds. CS.L=1, CS.D=0 */
+ return 4;
+ case 3: /* Invalid setting. CS.L=1, CS.D=1 */
+ /* fall through */
+ default:
+ return 0;
+ }
+}
+
+/**
* insn_get_reg_offset_modrm_rm - Obtain register in r/m part of ModRM byte
* @insn: Instruction structure containing the ModRM byte
* @regs: Set of registers indicated by the ModRM byte
--
2.9.3
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web