Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unix socket error 14: EFAULT (bad address)

I have a very simple question, but I have not managed to find any answers to it all weekend. I am using the sendto() function and it is returning error code 14: EFAULT. The man pages describe it as:

"An invalid user space address was specified for an argument."

I was convinced that this was talking about the IP address I was specifying, but now I suspect it may be the memory address of the message buffer that it is referring to - I can't find any clarification on this anywhere, can anyone clear this up?

like image 695
aktungmak Avatar asked Feb 13 '12 12:02

aktungmak


2 Answers

EFAULT It happen if the memory address of some argument passed to sendto (or more generally to any system call) is invalid. Think of it as a sort of SIGSEGV in kernel land regarding your syscall. For instance, if you pass a null or invalid buffer pointer (for reading, writing, sending, recieving...), you get that

See errno(3), sendto(2) etc... man pages.

EFAULT is not related to IP addresses at all.

like image 136
Basile Starynkevitch Avatar answered Nov 12 '22 03:11

Basile Starynkevitch


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.