3

This may be an odd question, but I am trying to find a way to break the gmtime_r function. I am writing a test for this block of code, but I cannot find a case to reach the else statement that does not include setting ptr or dates to NULL.

int main() {
    time_t ptr; // Actual storage for time_t
    struct tm dates; // Actual storage for struct tm
    time(&ptr); // Pointer to a time_t
    if(gmtime_r(&ptr, &dates)) { // Pointer to a time_t, pointer to a
        size_t a = 10;
    }
    else
    {
        size_t a = 20; //<-- trying to reach this else statement
    }
return 0;
}

Are there any valid and non-NULL values to set ptr or dates to that would bypass the if-statement and go into the else?

bmb
  • 361
  • 2
  • 13
  • As a side note, I think it is recommanded to use `ptr = time(NULL);`, which is ensured never to fail, instead of the pointer-variant. – Alceste_ Feb 02 '20 at 04:42
  • `time(&x);` or `x = time(NULL);` is purely a matter of personal preference, they fail or succeed in exactly the same conditions. The useful recommendation is not to call “ptr” a variable of type `time_t`, because `time_t` is not a pointer type. – Pascal Cuoq Sep 07 '22 at 10:45

2 Answers2

5

To provide an exact answer your question, we would need to know the implementation you're using to build your project.

Usually, standard headers like have public source, so you can search by yourself for the failure possibilities.

For glibc, I went down the rabbithole to give an example. (See source here)

In time/gmtime.c, we can see that gmtime_r is a weak alias for __gmtime_r, which returns __tz_convert directly.

In time/txset.c we see that __tz_convert can only return NULL if the __offtime function fails (returning 0).

Finally, in time/offtime.c, we see that:

  tp->tm_year = y - 1900;
  if (tp->tm_year != y - 1900)
    {
      /* The year cannot be represented due to overflow.  */
      __set_errno (EOVERFLOW);
      return 0;
    }

Which indicates a good clue about how we can make it fail. You can verify your idea with a short program.

e.g. Sample program to test out how to make gmtime_r fail

In my case, simply providing a large positive time does the trick, as the code uses integers for years, but time_t being a 64 bit value (on my system), I can have much more years than what int (on my system) can represent.

Of course, you could do the sample test program imediatly, without looking at the source.. but isn't it much more interesting that way? :P

Alceste_
  • 592
  • 4
  • 13
  • This is a really nice answer, but it would be much more helpful to post the code of that example program as a code block, instead of a screenshot. – NicholasM Feb 02 '20 at 05:30
  • But then I wouldn't have been able to show off that, btw, I use arch. :c -- On a more serious note, I wanted to show clearly how simple the whole process was and I just intuitively 'captured' the screen of it. Sadly enough, the files are all already deleted so I cannot edit it back to a text sample. :/ – Alceste_ Feb 02 '20 at 05:33
1

It is possible using the LD_PRELOAD trick. I'll not get really deep in the explanation of it, as this could be seen here, as well as in a short search in Google.

I'll show how I implemented it in C, for it to be valid for C users as well:

Let's have another C file and call it trick.c:

#define _GNU_SOURCE

#include <dlfcn.h>
#include <time.h>

typedef struct tm * (*gmtime_r_t)(const time_t *, struct tm *);

static int __fail = 0;

void __fail_gmtime_r() { __fail = 1; }
void __pass_gmtime_r() { __fail = 0; }

struct tm *gmtime_r(const time_t *timep, struct tm *result)
{
    if (__fail) { return NULL; }

    return ((gmtime_r_t)dlsym(RTLD_NEXT, "gmtime_r"))(timep, result);
}

Here, we implement our version of gmtime_r(), and we either call the original method, or fail it (returning NULL), depending on a flag (__fail).

We also expose two methods to set the value of this flag: __fail_gmtime_r(), and __pass_gmtime_r().

We compile this file into a shared object using the command line gcc trick.c -shared -fPIC -o trick.so -ldl.

I also implemented a small executable with a main() similar to yours for testing it, where I use the trick shared object. main.c:

#define _GNU_SOURCE

#include <dlfcn.h>
#include <time.h>
#include <stdio.h>

typedef void (*__fail_gmtime_r_t)();
typedef void (*__pass_gmtime_r_t)();

int main()
{
    __fail_gmtime_r_t __fail_gmtime_r = dlsym(RTLD_DEFAULT, "__fail_gmtime_r");
    __pass_gmtime_r_t __pass_gmtime_r = dlsym(RTLD_DEFAULT, "__pass_gmtime_r");

    time_t ptr;
    time(&ptr);

    struct tm dates;

    printf(gmtime_r(&ptr, &dates) ? "OK\n" : "FAIL\n");

    __fail_gmtime_r();
    printf(gmtime_r(&ptr, &dates) ? "OK\n" : "FAIL\n");

    __pass_gmtime_r();
    printf(gmtime_r(&ptr, &dates) ? "OK\n" : "FAIL\n");

    return 0;
}

And I compile it using the command line gcc main.c -o main -ldl.

Then, I used the following command in order to run the executable with the trick shared object replacing gmtime_r(): LD_PRELOAD=<FULL PATH TO trick.so> ./main.

You should call __fail_gmtime_r() and __pass_gmtime_r() from your main() to acheive the same effect.

I didn't go deep into explanations, but if it's something you'd like I have no problem in further explaining everything here.

Raz Rotenberg
  • 599
  • 4
  • 9