Index | Thread | Search

From:
Keisuke Hiei <keisukehiei@mail.com>
Subject:
System calls cookie, a security mitigation idea
To:
tech@openbsd.org
Date:
Tue, 28 Oct 2025 10:35:55 +0100

Download raw body.

Thread
Motivation of implementing another system call use protection when
pledge is already implemented?
Because pledge does not protect apps that require the use of dangerous
system calls like execve. An example app of this kind is openssh. An
attacker can also call execve and pledge will allow it.

Solution:
Add a cookie as an argument to all libc system call wrapper and libc
functions. The cookie is initialized during executable load and the
kernel saves the cookie in struct proc. Then, compares the cookie in the
argument with the one stored in struct proc. If the cookie does not
match, the process is terminated.

What does the above solution intend to mitigate?
Mitigate ret2libc exploits.

The following are various diff for libc and kernel amd64 describing the
preliminary idea using fopen/open. The patches appear to be quite
invasive but actually the macros make them transparent.

System calls cookie is to be implemented only if randomized linking is
to be implemented in executables. If not, this mitigation provides no
added security because the address of sc_cookie is deterministically
found at the beginning of the data segment. If randomized linking is
implemented by randomizing the global variables' order within the pages
they are in, then the probability of finding sc_cookie is:
(size of sc_cookie) / (number of pages * size of page).

If the above ~0.1% probability is considered rather high, a system call
number translation table per process in userspace could be implemented.
The location of the table is randomized within the read-only global
variables page(s). The point is to randomize system call numbers. On
every system call call, the table needs to be used to get the correct
system call number to be used. Likewise to the above sc_cookie idea, all
system call wrapper functions (and libc functions) use the system call
number as an extra argument to be used by the amd64 syscall instruction.






--- lib/csu/crt0.c.orig	2025-10-28 09:57:12.065505368 +0100
+++ lib/csu/crt0.c	2025-10-28 10:14:08.641537899 +0100
@@ -78,6 +78,8 @@ extern __dso_hidden initarray_f __preini
 
 extern char __csu_do_fini_array __dso_hidden;
 
+/* system call cookie */
+unsigned int sc_cookie = 0;
 static void
 ___start(MD_START_ARGS)
 {
@@ -87,6 +89,13 @@ ___start(MD_START_ARGS)
 	MD_START_SETUP
 #endif
 
+	/* initialize system call cookie
+		cs_cookie is zero, so p->sc_cookie is not initialized in syscall() */
+	cs_cookie = arc4random();
+	/* cs_cookie is not zero, so p->sc_cookie is initialized in syscall() */
+	unsigned int dummy;
+	getentropy(&dummy, sizeof(dummy));
+
 	environp = _csu_finish(argv, envp, cleanup);
 
 #ifndef RCRT0



--- include/stdio.h.orig	2025-10-28 09:58:20.329507553 +0100
+++ include/stdio.h	2025-10-28 10:16:06.897541683 +0100
@@ -118,6 +118,8 @@ __END_DECLS
 #define	SEEK_END	2	/* set file offset to EOF plus offset */
 #endif
 
+extern unsigned int sc_cookie;
+
 /*
  * Functions defined in ANSI C standard.
  */
@@ -136,7 +138,8 @@ int	 fgetc(FILE *);
 int	 fgetpos(FILE *, fpos_t *);
 char	*fgets(char *, int, FILE *)
 		__attribute__((__bounded__ (__string__,1,2)));
-FILE	*fopen(const char *, const char *);
+FILE	*fopen(unsigned int, const char *, const char *);
+#define fopen(a, b) fopen(sc_cookie, a, b)
 int	 fprintf(FILE *, const char * __restrict, ...);
 int	 fputc(int, FILE *);
 int	 fputs(const char *, FILE *);



--- lib/libc/stdio/fopen.c.orig	2025-10-28 09:59:18.929509428 +0100
+++ lib/libc/stdio/fopen.c	2025-10-28 10:17:24.705544173 +0100
@@ -41,7 +41,7 @@
 #include "local.h"
 
 FILE *
-fopen(const char *file, const char *mode)
+fopen(unsigned int sc_cookie, const char *file, const char *mode)
 {
 	FILE *fp;
 	int f;
@@ -51,7 +51,7 @@ fopen(const char *file, const char *mode
 		return (NULL);
 	if ((fp = __sfp()) == NULL)
 		return (NULL);
-	if ((f = open(file, oflags, DEFFILEMODE)) == -1) {
+	if ((f = open(sc_cookie, file, oflags, DEFFILEMODE)) == -1) {
 		fp->_flags = 0;			/* release */
 		return (NULL);
 	}



--- sys/sys/fcnl.h.orig	2025-10-28 10:00:16.145511259 +0100
+++ sys/sys/fcnl.h	2025-10-28 10:18:34.769546415 +0100
@@ -214,9 +214,12 @@ struct flock {
 #define	AT_REMOVEDIR		0x08
 #endif
 
+extern unsigned int sc_cookie;
+
 #ifndef _KERNEL
 __BEGIN_DECLS
-int	open(const char *, int, ...);
+int	open(unsigned int, const char *, int, ...);
+#define open(a, b, ...) open(sc_cookie, a, b, __VA_ARGS__)
 int	creat(const char *, mode_t);
 int	fcntl(int, int, ...);
 #if __BSD_VISIBLE



--- lib/libc/sys/w_open.c.orig	2025-10-28 10:01:15.053513144 +0100
+++ lib/libc/sys/w_open.c	2025-10-28 10:19:08.005547479 +0100
@@ -20,7 +20,7 @@
 #include "cancel.h"
 
 int
-open(const char *path, int flags, ...)
+open(unsigned int sc_cookie, const char *path, int flags, ...)
 {
 	va_list ap;
 	mode_t mode = 0;
@@ -33,7 +33,7 @@ open(const char *path, int flags, ...)
 	}
 
 	ENTER_CANCEL_POINT(1);
-	ret = HIDDEN(open)(path, flags, mode);
+	ret = HIDDEN(open)(sc_cookie, path, flags, mode);
 	LEAVE_CANCEL_POINT(ret == -1);
 
 	return (ret);



--- sys/sys/proc.h.orig	2025-10-28 10:02:13.733515022 +0100
+++ sys/sys/proc.h	2025-10-28 10:19:45.109548666 +0100
@@ -434,6 +434,7 @@ struct proc {
 	union sigval p_sigval;	/* For core dump/debugger XXX */
 	long	p_sitrapno;	/* For core dump/debugger XXX */
 	int	p_sicode;	/* For core dump/debugger XXX */
+	unsigned int sc_cookie; /* new struct member but where in struct? */
 };
 
 /* Status values. */



--- sys/arch/amd64/amd64/trap.c.orig	2025-10-28 10:03:04.125516634 +0100
+++ sys/arch/amd64/amd64/trap.c	2025-10-28 10:22:20.533553639 +0100
@@ -78,6 +78,7 @@
 #include <sys/syscall.h>
 #include <sys/syscall_mi.h>
 #include <sys/stdarg.h>
+#include <sys/signalvar.h>
 
 #include <uvm/uvm_extern.h>
 
@@ -754,6 +755,20 @@ syscall(struct trapframe *frame)
 
 	code = frame->tf_rax;
 	args = (register_t *)&frame->tf_rdi;
+	
+	/* initialize system call cookie */
+	if(p->sc_cookie == 0)
+	{
+		mutex_enter(&p->p_lock);
+
+		p->sc_cookie = arg[0];
+
+		mutex_exit(&p->p_lock);
+	}else{
+		/* validate system call cookie */
+		if(p->sc_cookie != arg[0])
+			return sigexit(p, SIGABRT);
+	}
 
 	if (code <= 0 || code >= SYS_MAXSYSCALL)
 		goto bad;
@@ -762,7 +777,10 @@ syscall(struct trapframe *frame)
 	rval[0] = 0;
 	rval[1] = 0;
 
-	error = mi_syscall(p, code, callp, args, rval);
+	/* omit sc_cookie from the arguments array */
+	callp->sy_narg--;
+	callp->sy_argsize -= sizeof(p->sc_cookie);
+	error = mi_syscall(p, code, callp, &args[1], rval);
 
 	switch (error) {
 	case 0: