4

My application is unable to handle time operations like time(2) when it runs in alpine docker container on an arm device.

What I have: I am building a native c application which is statically linked to musl with toolchain from musl.cc (arm-linux-musleabihf-gcc). I am using latest alpine container (without image tag).

How it behaves:

  • Running binary directly on arm device works as expected
  • Running in alpine container on x64 device works as expected
  • Running in alpine container on arm device does not work

What's going wrong:

  • time(NULL); returns ((time_t) -1) and error=1: "Operation not permitted"
  • The timestamps in the log outputs have cranked timestamps
  • SSH handshake fails because the validity of the remote certificate is in the future.

However, if I execute date in container's ash the output is valid. Thus, there seems to be a problem that only occurs in alpine containers on the ARM architecture. Funnily enough, I'm switching from Ubuntu to Alpine as we had similar problems there.

Does anyone have any idea what I am doing wrong?

Update #1: Same problem on ubuntu. So the problem seems to be on any docker basis image but only on arm devices.

Update #2: Here is a minimal example TimeTest.c

#include <stdio.h>
#include <string.h>
#include "time.h"
#include "errno.h"

int main()
{
    printf("hallo\n");

    time_t myTime;
    time_t result = time(&myTime);

    printf("result: %lld\n", result);

    if ((long)result < 0)
    {
        printf("time() error=%d: %s\n", errno, strerror(errno));
    }
    else
    {
        struct tm* tm_info = localtime(&myTime);
        printf("Current local time and date: %s\n", asctime(tm_info));
    }

    return 0;
}

CMakeLists.txt

cmake_minimum_required (VERSION 3.8)
project ("TimeTest")
if(BUILD_TARGET STREQUAL "ARM_MUSL")
    set(CMAKE_SYSTEM_PROCESSOR arm)
    set(CMAKE_C_COMPILER /usr/bin/arm-linux-musleabi-gcc)
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-stack-protector -mfloat-abi=softfp -static --static")
    set(CMAKE_LINK_SEARCH_END_STATIC TRUE)
endif()
add_executable (TimeTest "TimeTest.c")

Output on arm device

pi@raspberrypi:/tmp $ docker run --rm -it -v /tmp/TimeTest:/TimeTest alpine ash
/ # /TimeTest
hallo
result: -4696377169665647048
time() error=1: Operation not permitted
Thomas
  • 153
  • 9
  • Shot-in-the-dark guess: it might have something to do with [vDSO](https://en.wikipedia.org/wiki/VDSO). – Steve Summit Dec 23 '20 at 13:44
  • What happens if you call `time()` with the address of a `time_t` object? – Andrew Henle Dec 23 '20 at 14:28
  • @AndrewHenle: Both, the return value and the time_t pointed into time() have the same value: ((time_t) -1) – Thomas Dec 23 '20 at 14:40
  • Could you post sample program? `which is statically linked` Ugh, does the same happen with normal linking? Could you post the compilation command you are using? Ie. some [MCVE]. – KamilCuk Dec 23 '20 at 14:41
  • @KamilCuk: I added an example. Hope the example is OK – Thomas Dec 23 '20 at 15:25
  • Change `(long)result < 0` to `result == (time_t)-1`. Change `printf("result: %lld\n", result);` to `printf("result: %ju\n", (uintmax_t)result);` or `printf("result: %lld\n", (long long)result);`. So anyway, what is the current date on your machine setup to? – KamilCuk Dec 23 '20 at 15:27
  • I am getting "Segmentation fault". `result` seems to be not `(time_t)-1`. My `printf("result: %lld\n", result);` always returns an other value – Thomas Dec 23 '20 at 15:31
  • It might be [this issue](https://lwn.net/Articles/795128/). – Steve Summit Dec 23 '20 at 18:49
  • Which version of Docker are you using? – ionFish Dec 26 '20 at 12:39
  • @ionFish: I am using Docker version 19.03.8 – Thomas Dec 26 '20 at 15:00

3 Answers3

4

I am able to reproduce this exactly as described in the question. On my particular ARM hardware:

$ docker --version
Docker version 19.03.6, build 369ce74a3c [released approx. 2020-02-12]

$ file demo
demo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV),
statically linked, with debug_info, not stripped

Using strace we can observe this behavior inside a container launched as reported:

...
clock_gettime64(CLOCK_REALTIME, 0xbe9c5e10) = -1 EPERM (Operation not permitted)
...

However, if we add the --privileged flag to the Docker command, it works:

# ./demo
hallo
result: 1608983884
Current local time and date: Sat Dec 26 11:58:04 2020

This behavior is caused by a Docker issue: https://gitlab.alpinelinux.org/alpine/aports/-/issues/11774, which is fixed in this commit and rolled into Docker version 19.03.12 (?) and up.

ionFish
  • 1,004
  • 1
  • 8
  • 20
  • Thank you very much. This is really an annoying problem. I am afraid that I need a work around here. What do you think about a wrapper for `__clock_gettime()`? – Thomas Dec 26 '20 at 15:02
0

ionFish has solved the puzzle. I will mark his post as answer. However, I would like to post a possible work around that I think should work in all cases. What do you guys think?

I created a wrapper for __clock_gettime and always fallback to gettime32.

DockerFix.c

#include <time.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/syscall.h>

static bool __wrap___clock_gettime_fallback = false;

int __wrap___clock_gettime(clockid_t clk, struct timespec* ts)
{
    int r;

    if (!__wrap___clock_gettime_fallback)
    {
        r = __real___clock_gettime(clk, ts);
        if (!r) return r;

        if (errno == EPERM || errno == EINVAL)
        {
            printf("WARN: clock_gettime() faulted with '%s'. Using SYS_clock_gettime32 as permanent fallback. Possible cause could be a faulty Docker version!\n", strerror(errno));
            __wrap___clock_gettime_fallback = true;     // skip in future
        }
    }

    long ts32[2];
    r = syscall(SYS_clock_gettime32, clk, ts32);
    if (r == -ENOSYS && clk == CLOCK_REALTIME) {
        r = syscall(SYS_gettimeofday_time32, ts32, 0);
        ts32[1] *= 1000;
    }
    if (!r) {
        ts->tv_sec = ts32[0];
        ts->tv_nsec = ts32[1];
    }
    return r;
}

int __attribute__((weak, alias("__wrap___clock_gettime"))) clock_gettime(clockid_t clk, struct timespec* ts);

CMakeLists.txt

set(CMAKE_EXE_LINKER_FLAGS -Wl,--wrap=__clock_gettime")
add_executable (TimeTest "TimeTest.c" "DockerFix.c")
Thomas
  • 153
  • 9
0

for me in Raspbian SO, works correctly in a redmine image. These is the error that it reports:

Operation not permitted - clock_gettime (Errno::EPERM)

I use privileged:true in my docker compose file