When do Linux system calls trigger a segfault vs returning EFAULT?

1.1k views Asked by At

I'm trying to understand when clock_gettime() can lead to errors. The man page lists the following two possibilities:

  1. EFAULT tp points outside the accessible address space.
  2. EINVAL The clk_id specified is not supported on this system.

It's easy to trigger an EINVAL error but I'm not able to get clock_gettime() to set errno to EFAULT. Instead, the kernel sends a SIGSEGV signal to terminate the program. For instance, in the following code:

#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
    struct timespec tp;
    double time;

    if (clock_gettime(CLOCK_MONOTONIC, &tp + 4096) == -1) {
        if (errno == EINVAL) {
            perror("EINVAL");
            return EXIT_FAILURE;
        } else if (errno == EFAULT) {
            perror("EFAULT");
            return EXIT_FAILURE;
        } else {
            perror("something else");
            return EXIT_FAILURE;
        }
    }

    time = tp.tv_sec + 1e-9 * tp.tv_nsec;
    printf("%f\n", time);
}

How does the Linux kernel choose between triggering a segmentation fault and having the system call return -EINVAL? When will it choose to do the latter? If the kernel always sends the signal, is it actually necessary to check whether errno equals EFAULT?

I'm running Linux kernel 4.15 and I compiled the program with (using clang v6.0): clang -g -O0 -Wall -Wextra -Wshadow -Wstrict-aliasing -ansi -pedantic -Werror -std=gnu11 file.c -o file

2

There are 2 answers

0
R.. GitHub STOP HELPING ICE On BEST ANSWER

clock_gettime is probably not executing as a syscall, but rather in userspace as part of the vdso. If you actually perform a syscall by using the syscall function with SYS_clock_gettime as its argument, I would expect you to see EFAULT.

With that said, EFAULT is not ever something you should expect to be able to rely on. As soon as you pass an invalid pointer to a function that requires a valid pointer as part of its interface contract, you have undefined behavior, and a segfault or an error is only one possible manifestation among many. From this perspective it's something of a mistake that EFAULT is even documented.

0
KamilCuk On

I'm trying to understand when clock_gettime() can lead to errors.

Ok.

How does the Linux kernel choose between triggering a segmentation fault and having the system call return -EINVAL? When will it choose to do the latter?

It's easy. There are some checks in case they are true the function sets errno. In case you access a protected memory region the kernel sends SIGSEGV to your process.

If you inspect the __clock_gettime from glibc function you see that:

switch (clock_id)
    {
#ifdef SYSDEP_GETTIME
      SYSDEP_GETTIME;
#endif

#ifndef HANDLED_REALTIME
    case CLOCK_REALTIME:
      ...
      break;
#endif

    default:
#if HP_TIMING_AVAIL
      if ((clock_id ...) == CLOCK_THREAD_CPUTIME_ID)
           ...
      else
#endif
            __set_errno (EINVAL);
      break;

The glibc wrapper set's EINVAL in case of some strange clock_id value.

Dereferencing a pointer value outside any valid memory region in undefined behaviour and spawns nasal demons. On Linux a SIGSEGV is a signal sent to a process which tries to write to a protected memory region.

The following code spawns demons and should raise SIGSEGV:

struct timespec tp;
*(&tp + 4096) = (struct timespec){0};

so does the following code:

struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp + 4096)

If the kernel always sends the signal,

Not really. If it so just happens that sizeof(struct timespec) bytes starting from &tp + 4096 will not be inside protected memory region, the kernel will not send any signal, cause it would think, you write inside you own memory.

is it actually necessary to check whether errno equals EFAULT?

It's not necessary to check for any errors. I think you mix interpreting errors with checking for them. If you machine follows the specification you mentioned, if clock_gettime returns EFAULT you can write your program so it assumes that the underlying implementation on your machine of clock_gettime follows the linux manual page of clock_gettime. However, as you discovered, it does not, instead undefined behaviour happens and the kernel raises SIGSEGV. Which only means that the underlying implementation of the clock_gettime function does not follow the manual. The POSIX does not specify the EFAULT errno code. However I believe there may exists implementations which may return EFAULT errno or any other errno codes. However, what do you want your program to do when receiving EFAULT error? How to recover from such error? If these question bear any significance to you, then it may be reasonable to write an EFAULT handler for the clock_gettime function.

Please note, you are using Linux. Linux, kernel and glibc, mostly are licensed under the GNU General License or GNU Lesser General License, which has the following in it:

BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

The question bears down to trust: do you believe your system's clock_gettime() to follow the Linux manual implementation? I don't. If your system would be POSIX certificate, you could place some more trust in the functions that they will work as the manual says. No one guarantees you that, it is just a good will of many hardworking people that it works.