xref: /openbmc/linux/arch/x86/kernel/ioport.c (revision ead5d1f4d877e92c051e1a1ade623d0d30e71619)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2ccafa59aSmboton@gmail.com /*
3ccafa59aSmboton@gmail.com  * This contains the io-permission bitmap code - written by obz, with changes
4ccafa59aSmboton@gmail.com  * by Linus. 32/64 bits code unification by Miguel Botón.
5ccafa59aSmboton@gmail.com  */
6ccafa59aSmboton@gmail.com #include <linux/capability.h>
796c4f672SMatthew Garrett #include <linux/security.h>
8ccafa59aSmboton@gmail.com #include <linux/syscalls.h>
9da1016dfSAkinobu Mita #include <linux/bitmap.h>
10b800fc4dSThomas Gleixner #include <linux/ioport.h>
11b800fc4dSThomas Gleixner #include <linux/sched.h>
12b800fc4dSThomas Gleixner #include <linux/slab.h>
13b800fc4dSThomas Gleixner 
14577d5cd7SThomas Gleixner #include <asm/io_bitmap.h>
15b7ffc44dSAndy Lutomirski #include <asm/desc.h>
16cdcb58ccSBenjamin Thiel #include <asm/syscalls.h>
17ccafa59aSmboton@gmail.com 
18111e7b15SThomas Gleixner #ifdef CONFIG_X86_IOPL_IOPERM
19111e7b15SThomas Gleixner 
20060aa16fSThomas Gleixner static atomic64_t io_bitmap_sequence;
21060aa16fSThomas Gleixner 
io_bitmap_share(struct task_struct * tsk)224804e382SThomas Gleixner void io_bitmap_share(struct task_struct *tsk)
234804e382SThomas Gleixner {
24c8137aceSThomas Gleixner 	/* Can be NULL when current->thread.iopl_emul == 3 */
25c8137aceSThomas Gleixner 	if (current->thread.io_bitmap) {
264804e382SThomas Gleixner 		/*
274804e382SThomas Gleixner 		 * Take a refcount on current's bitmap. It can be used by
284804e382SThomas Gleixner 		 * both tasks as long as none of them changes the bitmap.
294804e382SThomas Gleixner 		 */
304804e382SThomas Gleixner 		refcount_inc(&current->thread.io_bitmap->refcnt);
314804e382SThomas Gleixner 		tsk->thread.io_bitmap = current->thread.io_bitmap;
32c8137aceSThomas Gleixner 	}
334804e382SThomas Gleixner 	set_tsk_thread_flag(tsk, TIF_IO_BITMAP);
344804e382SThomas Gleixner }
354804e382SThomas Gleixner 
task_update_io_bitmap(struct task_struct * tsk)36*4bfe6cceSJay Lang static void task_update_io_bitmap(struct task_struct *tsk)
37c8137aceSThomas Gleixner {
38*4bfe6cceSJay Lang 	struct thread_struct *t = &tsk->thread;
39c8137aceSThomas Gleixner 
40c8137aceSThomas Gleixner 	if (t->iopl_emul == 3 || t->io_bitmap) {
41c8137aceSThomas Gleixner 		/* TSS update is handled on exit to user space */
42*4bfe6cceSJay Lang 		set_tsk_thread_flag(tsk, TIF_IO_BITMAP);
43c8137aceSThomas Gleixner 	} else {
44*4bfe6cceSJay Lang 		clear_tsk_thread_flag(tsk, TIF_IO_BITMAP);
45c8137aceSThomas Gleixner 		/* Invalidate TSS */
46c8137aceSThomas Gleixner 		preempt_disable();
47c8137aceSThomas Gleixner 		tss_update_io_bitmap();
48c8137aceSThomas Gleixner 		preempt_enable();
49c8137aceSThomas Gleixner 	}
50c8137aceSThomas Gleixner }
51c8137aceSThomas Gleixner 
io_bitmap_exit(struct task_struct * tsk)52*4bfe6cceSJay Lang void io_bitmap_exit(struct task_struct *tsk)
53ea5f1cd7SThomas Gleixner {
54*4bfe6cceSJay Lang 	struct io_bitmap *iobm = tsk->thread.io_bitmap;
55ea5f1cd7SThomas Gleixner 
56*4bfe6cceSJay Lang 	tsk->thread.io_bitmap = NULL;
57*4bfe6cceSJay Lang 	task_update_io_bitmap(tsk);
584804e382SThomas Gleixner 	if (iobm && refcount_dec_and_test(&iobm->refcnt))
59ea5f1cd7SThomas Gleixner 		kfree(iobm);
60ea5f1cd7SThomas Gleixner }
61ea5f1cd7SThomas Gleixner 
62ccafa59aSmboton@gmail.com /*
63be9afb4bSThomas Gleixner  * This changes the io permissions bitmap in the current task.
64ccafa59aSmboton@gmail.com  */
ksys_ioperm(unsigned long from,unsigned long num,int turn_on)6566f4e88cSDominik Brodowski long ksys_ioperm(unsigned long from, unsigned long num, int turn_on)
66ccafa59aSmboton@gmail.com {
67ccafa59aSmboton@gmail.com 	struct thread_struct *t = &current->thread;
6822fe5b04SThomas Gleixner 	unsigned int i, max_long;
69577d5cd7SThomas Gleixner 	struct io_bitmap *iobm;
70ccafa59aSmboton@gmail.com 
71ccafa59aSmboton@gmail.com 	if ((from + num <= from) || (from + num > IO_BITMAP_BITS))
72ccafa59aSmboton@gmail.com 		return -EINVAL;
7396c4f672SMatthew Garrett 	if (turn_on && (!capable(CAP_SYS_RAWIO) ||
7496c4f672SMatthew Garrett 			security_locked_down(LOCKDOWN_IOPORT)))
75ccafa59aSmboton@gmail.com 		return -EPERM;
76ccafa59aSmboton@gmail.com 
77ccafa59aSmboton@gmail.com 	/*
78ccafa59aSmboton@gmail.com 	 * If it's the first ioperm() call in this thread's lifetime, set the
79ccafa59aSmboton@gmail.com 	 * IO bitmap up. ioperm() is much less timing critical than clone(),
80ccafa59aSmboton@gmail.com 	 * this is why we delay this operation until now:
81ccafa59aSmboton@gmail.com 	 */
82577d5cd7SThomas Gleixner 	iobm = t->io_bitmap;
83577d5cd7SThomas Gleixner 	if (!iobm) {
8432f3bf67SThomas Gleixner 		/* No point to allocate a bitmap just to clear permissions */
8532f3bf67SThomas Gleixner 		if (!turn_on)
8632f3bf67SThomas Gleixner 			return 0;
87577d5cd7SThomas Gleixner 		iobm = kmalloc(sizeof(*iobm), GFP_KERNEL);
88577d5cd7SThomas Gleixner 		if (!iobm)
89ccafa59aSmboton@gmail.com 			return -ENOMEM;
90ccafa59aSmboton@gmail.com 
91577d5cd7SThomas Gleixner 		memset(iobm->bitmap, 0xff, sizeof(iobm->bitmap));
924804e382SThomas Gleixner 		refcount_set(&iobm->refcnt, 1);
93ccafa59aSmboton@gmail.com 	}
94ccafa59aSmboton@gmail.com 
95ccafa59aSmboton@gmail.com 	/*
964804e382SThomas Gleixner 	 * If the bitmap is not shared, then nothing can take a refcount as
974804e382SThomas Gleixner 	 * current can obviously not fork at the same time. If it's shared
984804e382SThomas Gleixner 	 * duplicate it and drop the refcount on the original one.
994804e382SThomas Gleixner 	 */
1004804e382SThomas Gleixner 	if (refcount_read(&iobm->refcnt) > 1) {
1014804e382SThomas Gleixner 		iobm = kmemdup(iobm, sizeof(*iobm), GFP_KERNEL);
1024804e382SThomas Gleixner 		if (!iobm)
1034804e382SThomas Gleixner 			return -ENOMEM;
1044804e382SThomas Gleixner 		refcount_set(&iobm->refcnt, 1);
105*4bfe6cceSJay Lang 		io_bitmap_exit(current);
1064804e382SThomas Gleixner 	}
1074804e382SThomas Gleixner 
1084804e382SThomas Gleixner 	/*
1094804e382SThomas Gleixner 	 * Store the bitmap pointer (might be the same if the task already
1104804e382SThomas Gleixner 	 * head one). Must be done here so freeing the bitmap when all
1114804e382SThomas Gleixner 	 * permissions are dropped has the pointer set up.
1124804e382SThomas Gleixner 	 */
1134804e382SThomas Gleixner 	t->io_bitmap = iobm;
1144804e382SThomas Gleixner 	/* Mark it active for context switching and exit to user mode */
1154804e382SThomas Gleixner 	set_thread_flag(TIF_IO_BITMAP);
1164804e382SThomas Gleixner 
1174804e382SThomas Gleixner 	/*
11822fe5b04SThomas Gleixner 	 * Update the tasks bitmap. The update of the TSS bitmap happens on
11922fe5b04SThomas Gleixner 	 * exit to user mode. So this needs no protection.
120ccafa59aSmboton@gmail.com 	 */
121da1016dfSAkinobu Mita 	if (turn_on)
122577d5cd7SThomas Gleixner 		bitmap_clear(iobm->bitmap, from, num);
123da1016dfSAkinobu Mita 	else
124577d5cd7SThomas Gleixner 		bitmap_set(iobm->bitmap, from, num);
125ccafa59aSmboton@gmail.com 
126ccafa59aSmboton@gmail.com 	/*
127ccafa59aSmboton@gmail.com 	 * Search for a (possibly new) maximum. This is simple and stupid,
128ccafa59aSmboton@gmail.com 	 * to keep it obviously correct:
129ccafa59aSmboton@gmail.com 	 */
130ea5f1cd7SThomas Gleixner 	max_long = UINT_MAX;
131ae31cea8SThomas Gleixner 	for (i = 0; i < IO_BITMAP_LONGS; i++) {
132577d5cd7SThomas Gleixner 		if (iobm->bitmap[i] != ~0UL)
133ccafa59aSmboton@gmail.com 			max_long = i;
134ae31cea8SThomas Gleixner 	}
135ea5f1cd7SThomas Gleixner 	/* All permissions dropped? */
136ea5f1cd7SThomas Gleixner 	if (max_long == UINT_MAX) {
137*4bfe6cceSJay Lang 		io_bitmap_exit(current);
138ea5f1cd7SThomas Gleixner 		return 0;
139ea5f1cd7SThomas Gleixner 	}
140ccafa59aSmboton@gmail.com 
14122fe5b04SThomas Gleixner 	iobm->max = (max_long + 1) * sizeof(unsigned long);
142ccafa59aSmboton@gmail.com 
143ae31cea8SThomas Gleixner 	/*
1444804e382SThomas Gleixner 	 * Update the sequence number to force a TSS update on return to
1454804e382SThomas Gleixner 	 * user mode.
146ae31cea8SThomas Gleixner 	 */
1474804e382SThomas Gleixner 	iobm->sequence = atomic64_add_return(1, &io_bitmap_sequence);
148ccafa59aSmboton@gmail.com 
149ccafa59aSmboton@gmail.com 	return 0;
150ccafa59aSmboton@gmail.com }
151ccafa59aSmboton@gmail.com 
SYSCALL_DEFINE3(ioperm,unsigned long,from,unsigned long,num,int,turn_on)15266f4e88cSDominik Brodowski SYSCALL_DEFINE3(ioperm, unsigned long, from, unsigned long, num, int, turn_on)
15366f4e88cSDominik Brodowski {
15466f4e88cSDominik Brodowski 	return ksys_ioperm(from, num, turn_on);
15566f4e88cSDominik Brodowski }
15666f4e88cSDominik Brodowski 
157ccafa59aSmboton@gmail.com /*
158be9afb4bSThomas Gleixner  * The sys_iopl functionality depends on the level argument, which if
159a24ca997SThomas Gleixner  * granted for the task is used to enable access to all 65536 I/O ports.
160ccafa59aSmboton@gmail.com  *
161a24ca997SThomas Gleixner  * This does not use the IOPL mechanism provided by the CPU as that would
162a24ca997SThomas Gleixner  * also allow the user space task to use the CLI/STI instructions.
163be9afb4bSThomas Gleixner  *
164a24ca997SThomas Gleixner  * Disabling interrupts in a user space task is dangerous as it might lock
165a24ca997SThomas Gleixner  * up the machine and the semantics vs. syscalls and exceptions is
166a24ca997SThomas Gleixner  * undefined.
167be9afb4bSThomas Gleixner  *
168a24ca997SThomas Gleixner  * Setting IOPL to level 0-2 is disabling I/O permissions. Level 3
169a24ca997SThomas Gleixner  * 3 enables them.
170be9afb4bSThomas Gleixner  *
171be9afb4bSThomas Gleixner  * IOPL is strictly per thread and inherited on fork.
172ccafa59aSmboton@gmail.com  */
SYSCALL_DEFINE1(iopl,unsigned int,level)173b3af11afSAl Viro SYSCALL_DEFINE1(iopl, unsigned int, level)
174a1bf250aSChris Wright {
17527f59559SBrian Gerst 	struct thread_struct *t = &current->thread;
176c8137aceSThomas Gleixner 	unsigned int old;
177a1bf250aSChris Wright 
178a1bf250aSChris Wright 	if (level > 3)
179a1bf250aSChris Wright 		return -EINVAL;
180c8137aceSThomas Gleixner 
181c8137aceSThomas Gleixner 	old = t->iopl_emul;
182c8137aceSThomas Gleixner 
183c8137aceSThomas Gleixner 	/* No point in going further if nothing changes */
184c8137aceSThomas Gleixner 	if (level == old)
185c8137aceSThomas Gleixner 		return 0;
186c8137aceSThomas Gleixner 
187a1bf250aSChris Wright 	/* Trying to gain more privileges? */
188a1bf250aSChris Wright 	if (level > old) {
18996c4f672SMatthew Garrett 		if (!capable(CAP_SYS_RAWIO) ||
19096c4f672SMatthew Garrett 		    security_locked_down(LOCKDOWN_IOPORT))
191a1bf250aSChris Wright 			return -EPERM;
192a1bf250aSChris Wright 	}
193c8137aceSThomas Gleixner 
194c8137aceSThomas Gleixner 	t->iopl_emul = level;
195*4bfe6cceSJay Lang 	task_update_io_bitmap(current);
196a1bf250aSChris Wright 
197a1bf250aSChris Wright 	return 0;
198a1bf250aSChris Wright }
199111e7b15SThomas Gleixner 
200111e7b15SThomas Gleixner #else /* CONFIG_X86_IOPL_IOPERM */
201111e7b15SThomas Gleixner 
ksys_ioperm(unsigned long from,unsigned long num,int turn_on)202111e7b15SThomas Gleixner long ksys_ioperm(unsigned long from, unsigned long num, int turn_on)
203111e7b15SThomas Gleixner {
204111e7b15SThomas Gleixner 	return -ENOSYS;
205111e7b15SThomas Gleixner }
SYSCALL_DEFINE3(ioperm,unsigned long,from,unsigned long,num,int,turn_on)206111e7b15SThomas Gleixner SYSCALL_DEFINE3(ioperm, unsigned long, from, unsigned long, num, int, turn_on)
207111e7b15SThomas Gleixner {
208111e7b15SThomas Gleixner 	return -ENOSYS;
209111e7b15SThomas Gleixner }
210111e7b15SThomas Gleixner 
SYSCALL_DEFINE1(iopl,unsigned int,level)211111e7b15SThomas Gleixner SYSCALL_DEFINE1(iopl, unsigned int, level)
212111e7b15SThomas Gleixner {
213111e7b15SThomas Gleixner 	return -ENOSYS;
214111e7b15SThomas Gleixner }
215111e7b15SThomas Gleixner #endif
216