Minimal runnable example with getcpu
Just to make things more concrete, we can have a look at the getcpu
system call, which is very simple to understand, and shows the same EFAULT behaviour.
From man getcpu
we see that the signature is:
int getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache);
and the memory pointed to by the cpu
will contain the ID of the current CPU the process is running on after the syscall, the only possible error being:
ERRORS
EFAULT Arguments point outside the calling process's address space.
So we can test it out with:
main.c
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
int main(void) {
int err, ret;
unsigned cpu;
/* Correct operation. */
assert(syscall(SYS_getcpu, &cpu, NULL, NULL) == 0);
printf("%u\n", cpu);
/* Bad trash address == 1. */
ret = syscall(SYS_getcpu, 1, NULL, NULL);
err = errno;
assert(ret == -1);
printf("%d\n", err);
perror("getcpu");
return EXIT_SUCCESS;
}
compile and run:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out
Sample output:
cpu 3
errno 14
getcpu: Bad address
so we see that the bad call with a trash address of 1
returned 14
, which is EFAULT as seen from kernel code: https://stackoverflow.com/a/53958705/895245
Remember that the syscall itself returns -14
, and then the syscall
C wrapper detects that it is an error due to being negative, returns -1
, and sets errno
to the actual precise error code.
And since the syscall is so simple, we can confirm this from the kernel 5.4 implementation as well at kernel/sys.c
:
SYSCALL_DEFINE3(getcpu, unsigned __user *, cpup, unsigned __user *, nodep,
struct getcpu_cache __user *, unused)
{
int err = 0;
int cpu = raw_smp_processor_id();
if (cpup)
err |= put_user(cpu, cpup);
if (nodep)
err |= put_user(cpu_to_node(cpu), nodep);
return err ? -EFAULT : 0;
}
so clearly we see that -EFAULT
is returned if there is a problem with put_user
.
It is worth mentioning that my glibc does have a getcpu
wrapper as well in sched.h
, but that implementation segfaults in case of bad addresses, which is a bit confusing: How do I include Linux header files like linux/getcpu.h? But it is not what the actual syscall does to the process, just whatever glibc is doing with that address.
Tested on Ubuntu 20.04, Linux 5.4.