Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/iopl: Fake iopl(3) CLI/STI usage

Since commit c8137ace5638 ("x86/iopl: Restrict iopl() permission
scope") it's possible to emulate iopl(3) using ioperm(), except for
the CLI/STI usage.

Userspace CLI/STI usage is very dubious (read broken), since any
exception taken during that window can lead to rescheduling anyway (or
worse). The IOPL(2) manpage even states that usage of CLI/STI is highly
discouraged and might even crash the system.

Of course, that won't stop people and HP has the dubious honour of
being the first vendor to be found using this in their hp-health
package.

In order to enable this 'software' to still 'work', have the #GP treat
the CLI/STI instructions as NOPs when iopl(3). Warn the user that
their program is doing dubious things.

Fixes: a24ca9976843 ("x86/iopl: Remove legacy IOPL option")
Reported-by: Ondrej Zary <linux@zary.sk>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Cc: stable@kernel.org # v5.5+
Link: https://lkml.kernel.org/r/20210918090641.GD5106@worktop.programming.kicks-ass.net

+37 -1
+1
arch/x86/include/asm/insn-eval.h
··· 21 21 int insn_get_modrm_reg_off(struct insn *insn, struct pt_regs *regs); 22 22 unsigned long insn_get_seg_base(struct pt_regs *regs, int seg_reg_idx); 23 23 int insn_get_code_seg_params(struct pt_regs *regs); 24 + int insn_get_effective_ip(struct pt_regs *regs, unsigned long *ip); 24 25 int insn_fetch_from_user(struct pt_regs *regs, 25 26 unsigned char buf[MAX_INSN_SIZE]); 26 27 int insn_fetch_from_user_inatomic(struct pt_regs *regs,
+1
arch/x86/include/asm/processor.h
··· 518 518 */ 519 519 unsigned long iopl_emul; 520 520 521 + unsigned int iopl_warn:1; 521 522 unsigned int sig_on_uaccess_err:1; 522 523 523 524 /*
+1
arch/x86/kernel/process.c
··· 132 132 frame->ret_addr = (unsigned long) ret_from_fork; 133 133 p->thread.sp = (unsigned long) fork_frame; 134 134 p->thread.io_bitmap = NULL; 135 + p->thread.iopl_warn = 0; 135 136 memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps)); 136 137 137 138 #ifdef CONFIG_X86_64
+33
arch/x86/kernel/traps.c
··· 528 528 529 529 #define GPFSTR "general protection fault" 530 530 531 + static bool fixup_iopl_exception(struct pt_regs *regs) 532 + { 533 + struct thread_struct *t = &current->thread; 534 + unsigned char byte; 535 + unsigned long ip; 536 + 537 + if (!IS_ENABLED(CONFIG_X86_IOPL_IOPERM) || t->iopl_emul != 3) 538 + return false; 539 + 540 + if (insn_get_effective_ip(regs, &ip)) 541 + return false; 542 + 543 + if (get_user(byte, (const char __user *)ip)) 544 + return false; 545 + 546 + if (byte != 0xfa && byte != 0xfb) 547 + return false; 548 + 549 + if (!t->iopl_warn && printk_ratelimit()) { 550 + pr_err("%s[%d] attempts to use CLI/STI, pretending it's a NOP, ip:%lx", 551 + current->comm, task_pid_nr(current), ip); 552 + print_vma_addr(KERN_CONT " in ", ip); 553 + pr_cont("\n"); 554 + t->iopl_warn = 1; 555 + } 556 + 557 + regs->ip += 1; 558 + return true; 559 + } 560 + 531 561 DEFINE_IDTENTRY_ERRORCODE(exc_general_protection) 532 562 { 533 563 char desc[sizeof(GPFSTR) + 50 + 2*sizeof(unsigned long) + 1] = GPFSTR; ··· 583 553 tsk = current; 584 554 585 555 if (user_mode(regs)) { 556 + if (fixup_iopl_exception(regs)) 557 + goto exit; 558 + 586 559 tsk->thread.error_code = error_code; 587 560 tsk->thread.trap_nr = X86_TRAP_GP; 588 561
+1 -1
arch/x86/lib/insn-eval.c
··· 1417 1417 } 1418 1418 } 1419 1419 1420 - static int insn_get_effective_ip(struct pt_regs *regs, unsigned long *ip) 1420 + int insn_get_effective_ip(struct pt_regs *regs, unsigned long *ip) 1421 1421 { 1422 1422 unsigned long seg_base = 0; 1423 1423