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(¤t->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 = ¤t->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 = ¤t->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