openresty / openresty/openresty

[Windows x64] Log timestamps fall back to 1970 after 2038 due to 32-bit `timeval.tv_sec` overflow

Open
#1,150 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
14k
Forks
1.6k
PR merge metrics
No merged PRs in 30d

Description

Summary

On Windows x64, OpenResty log timestamps fall back to a date around 1970 when the Windows system clock is set past 2038-01-19 03:14:07 UTC.

The tested build has a 64-bit time_t, but the Win32 implementation of ngx_gettimeofday() stores Unix seconds through a 32-bit long. At 2038-01-19 03:14:08 UTC, the conversion overflows to a negative value. ngx_gmtime() later clamps that negative value to zero, so the cached log time is formatted as the Unix Epoch.

Environment

  • OpenResty: 1.31.1.1
  • Bundled nginx core: 1.31.1
  • ngx_lua: 0.10.31rc5
  • OpenSSL: 3.1.5
  • Compiler: GCC 16.1.0, MSYS2 MinGW64
  • Build options: -O2, 64-bit target
  • Operating system: Windows 25H2 x64, build 26200.9168
  • sizeof(time_t): 8
  • sizeof(long): 4

Relevant nginx -V output:

nginx version: openresty/1.31.1.1
built by gcc 16.1.0 (Rev6, Built by MSYS2 project)
built with OpenSSL 3.1.5 30 Jan 2024

Minimal configuration

worker_processes  1;
error_log  logs/error.log notice;

events {
    worker_connections  64;
}

http {
    log_format y2038 '$time_iso8601 $msec "$request" $status';
    access_log logs/access.log y2038;

    server {
        listen 18080;

        location / {
            return 200 "ok\n";
        }
    }
}

Steps to reproduce

  1. Stop OpenResty.
  2. Set the Windows system clock to 2039-01-01 or another date after the 2038 boundary. Automatic time synchronization may need to be disabled temporarily.
  3. Start OpenResty with the configuration above.
  4. Run curl.exe http://127.0.0.1:18080/.
  5. Inspect logs/access.log and logs/error.log.
  6. Restore automatic time synchronization after the test.

Expected behavior

The log entries should contain the configured system date in 2039.

Actual behavior

The log timestamp falls back to a date around 1970. The exact displayed hour depends on the local time zone and log format, but the year is 1970 instead of 2039.

Root cause

The Win32 implementation obtains a correct 64-bit FILETIME, but converts the seconds to long when filling struct timeval:

intervals -= 116444736000000000;

tp->tv_sec = (long) (intervals / 10000000);
tp->tv_usec = (long) ((intervals % 10000000) / 10);

Windows uses the LLP64 data model, so long remains 32 bits in a 64-bit process. Although time_t is 64 bits in this build, both the cast and the Win32 struct timeval.tv_sec field truncate the Unix timestamp to 32 bits.

At the boundary:

2038-01-19 03:14:08 UTC = 2147483648 Unix seconds

the converted value becomes negative. ngx_gmtime() in src/core/ngx_times.c then normalizes it to zero:

if (t < 0) {
    t = 0;
}

The observed path is therefore:

GetSystemTimeAsFileTime()
    -> correct 64-bit FILETIME
    -> ngx_gettimeofday()
    -> conversion through 32-bit long
    -> negative Unix timestamp
    -> ngx_gmtime() clamps the value to zero
    -> the cached log time is formatted as a date in 1970

Suggested direction

Avoid storing Unix seconds through long in the Windows implementation. One source-level approach that has been validated locally is to use an nginx-owned timeval type whose seconds field is time_t:

typedef struct {
    time_t  tv_sec;
    long    tv_usec;
} ngx_timeval_t;

The Win32 conversion can then assign the seconds to time_t, while Unix keeps its existing ABI internally through an alias:

typedef struct timeval ngx_timeval_t;

This is only a proposed implementation direction. I am happy to adjust the internal API shape based on maintainer feedback, especially if compatibility with third-party modules requires a different approach.

Validation already performed

The source-level fix was tested with the following values:

  • Unix Epoch
  • 2038-01-19 03:14:07 UTC
  • 2038-01-19 03:14:08 UTC
  • A timestamp in 2039, including microseconds
  • 2106-02-07 06:28:16 UTC, corresponding to Unix timestamp 2^32

The following checks passed on the OpenResty 1.31.1.1 test build:

  • Full Windows x64 build
  • 8/8 runtime verification cases
  • Correct access and error log timestamps after the 2038 boundary
  • Offline clean rebuild
  • Source archive inventory check
  • Object-code inspection at -O2; the conversion helper was inlined

The implementation adds no system calls, locks, heap allocations, or extra time-update operations.

Upstream nginx tracking

The root cause is in nginx's Win32 platform layer and is tracked upstream:

This OpenResty issue records the downstream impact and can track when
the nginx fix is accepted or included in a bundled nginx release.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Inspect the Windows implementation of ngx_gettimeofday() and src/core/ngx_times.c, then compare the proposed internal timeval type with nginx issue 1729 and pull request 1730. Reproduce the post-2038 cases in a Windows x64 build and verify that access and error log timestamps remain correct through the listed boundary and 2039 checks.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, nginx
Domain
operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.