Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Potential NULL Pointer Dereference in ngx_timezone_update #564

Open
hpkit opened this issue Mar 7, 2025 · 3 comments
Open

Potential NULL Pointer Dereference in ngx_timezone_update #564

hpkit opened this issue Mar 7, 2025 · 3 comments

Comments

@hpkit
Copy link

hpkit commented Mar 7, 2025

Hello! I analyzed Nginx with Svace static analyzer. It found a potential problem in the code in /src/os/unix/ngx_time.c

Problem Description

The function ngx_timezone_update contains a potential runtime error when using the localtime function. Specifically:

  1. Potential NULL Pointer Dereference:

    • The localtime function may return NULL if an error occurs or if the input pointer (&s) is invalid.
    • In the original code, there is no check for whether localtime returns NULL. As a result, passing the returned value (t) directly to strftime could lead to a dereference of a NULL pointer, causing undefined behavior or a program crash.
  2. Buffer Size Issue:

    • The buf array is defined with a size of 4 bytes, but the %H format in strftime generates a string like "HH" (e.g., "12") plus a null terminator (\0). This requires at least 3 bytes for the string and 1 byte for the null terminator, totaling 4 bytes. However, it is safer to allocate one extra byte to prevent potential overflow issues.
  3. Use of 0 Instead of NULL:

    • While time(0) works because 0 is interpreted as a null pointer in this context, it is less clear and less portable than explicitly using NULL. Modern coding standards recommend using NULL for better readability and compatibility.

Solution

To address these issues, the following changes were made:

  1. Check for NULL Return from localtime:

    • Added a check after calling localtime to ensure that the returned pointer (t) is not NULL.
    • If t is NULL, log an error message using ngx_log_error and exit the function early to prevent further execution with invalid data.
  2. Increase Buffer Size:

    • Changed the size of the buf array from 4 to 5 to ensure there is enough space for the formatted string and the null terminator.
  3. Replace 0 with NULL:

    • Replaced time(0) with time(NULL) for better clarity and adherence to modern coding standards.

Impact of the Fix

These changes improve the robustness and safety of the code by:

  • Preventing potential crashes caused by dereferencing a NULL pointer.
  • Ensuring proper handling of edge cases where localtime might fail.
  • Enhancing code readability and maintainability by adhering to best practices.

This patch ensures that the function behaves correctly even in unexpected scenarios, making it more reliable for production use.

--- ngx_time.c	2025-02-05 14:07:30.000000000 +0300
+++ ngx_time.c.patched	2025-03-07 09:31:55.362193539 +0300
@@ -41,13 +41,19 @@
 #elif (NGX_LINUX)
     time_t      s;
     struct tm  *t;
-    char        buf[4];
+    char        buf[5];
 
-    s = time(0);
+    s = time(NULL);
 
     t = localtime(&s);
+   
+    if (t == NULL) {
+        // Handle error: localtime failed.
+        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "localtime() failed");
+        return;
+    }
 
-    strftime(buf, 4, "%H", t);
+    strftime(buf, sizeof(buf), "%H", t)
 
 #endif
 }

Found by Linux Verification Center (linuxtesting.org) with SVACE.

@ac000
Copy link
Member

ac000 commented Mar 13, 2025

Hi,

Hello! I analyzed Nginx with Svace static analyzer. It found a potential problem in the code in /src/os/unix/ngx_time.c

Problem Description

The function ngx_timezone_update contains a potential runtime error when using the localtime function. Specifically:

1. **Potential NULL Pointer Dereference**:
   
   * The `localtime` function may return `NULL` if an error occurs or if the input pointer (`&s`) is invalid.
   * In the original code, there is no check for whether `localtime` returns `NULL`. As a result, passing the returned value (`t`) directly to `strftime` could lead to a dereference of a `NULL` pointer, causing undefined behavior or a program crash.

Technically true, but I'm not sure how likely this is seeing as we're using the value as returned from time(2).

2. **Buffer Size Issue**:
   
   * The `buf` array is defined with a size of 4 bytes, but the `%H` format in `strftime` generates a string like `"HH"` (e.g., `"12"`) plus a null terminator (`\0`). This requires at least 3 bytes for the string and 1 byte for the null terminator, totaling 4 bytes. However, it is safer to allocate one extra byte to prevent potential overflow issues.

Hmm, this seems wrong. "HH\0" is 3 bytes. So even the current value of 4 is more than enough...

3. **Use of `0` Instead of `NULL`**:
   
   * While `time(0)` works because `0` is interpreted as a null pointer in this context, it is less clear and less portable than explicitly using `NULL`. Modern coding standards recommend using `NULL` for better readability and compatibility.

Good change (IMHO)

Impact of the Fix

Fixes ... ;)

This patch ensures that the function behaves correctly even in unexpected scenarios, making it more reliable for production use.

--- ngx_time.c 2025-02-05 14:07:30.000000000 +0300
+++ ngx_time.c.patched 2025-03-07 09:31:55.362193539 +0300
@@ -41,13 +41,19 @@
#elif (NGX_LINUX)
time_t s;
struct tm *t;

  • char buf[4];
  • char buf[5];

Not required.

  • s = time(0);
  • s = time(NULL);

Good change.

 t = localtime(&s);
  • if (t == NULL) {

I'm ambivalent about the NULL check here, it really shouldn't happen... with time(NULL), it can't fail and its return value must surely be a valid number of seconds since the epoch...

  •    // Handle error: localtime failed.
    

I don't think the comment is needed.

  •    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "localtime() failed");
    
  •    return;
    
  • }
  • strftime(buf, 4, "%H", t);
  • strftime(buf, sizeof(buf), "%H", t)

Is what I would probably do...

#endif
}

Found by Linux Verification Center (linuxtesting.org) with SVACE.

@hpkit
Copy link
Author

hpkit commented Mar 19, 2025

Analysis of the Argument:

  1. time(NULL) Cannot Fail:

    • The time(NULL) function returns the current time as the number of seconds since the Unix epoch (January 1, 1970). On success, it returns a value of type time_t. If an error occurs, it returns (time_t)(-1).
    • In most cases, time(NULL) works correctly and returns a valid timestamp. However, there are rare scenarios where it could fail (e.g., if the system cannot retrieve the current time).
  2. Can localtime Return NULL?:

    • Even if time(NULL) returns a valid timestamp, the localtime function can still return NULL in certain cases:
      • If the input value is outside the range supported by the system for local time conversion.
      • If an error occurs during the time conversion process (e.g., issues with locale settings or internal data structures).
      • On some platforms or under specific conditions (e.g., very large or small timestamps).
  3. Why Is the NULL Check Important?:

    • While time(NULL) usually works correctly, localtime may fail due to various reasons, such as:
      • Extremely large or small timestamps that exceed the system's supported range.
      • Misconfigured system settings (e.g., incorrect time zones or locales).
      • Internal errors in the implementation of localtime.
    • Ignoring the NULL check can lead to a segmentation fault when calling strftime with a null pointer (t == NULL).
  4. Risk of Ignoring the Check:

    • If the NULL check is omitted and localtime returns NULL, the program will crash when strftime attempts to dereference the null pointer.
    • This crash could occur even in cases where the issue could have been easily handled by adding a simple check.

@ac000
Copy link
Member

ac000 commented Mar 19, 2025

Analysis of the Argument:

1. **`time(NULL)` Cannot Fail**:
   
   * The `time(NULL)` function returns the current time as the number of seconds since the Unix epoch (January 1, 1970). On success, it returns a value of type `time_t`. If an error occurs, it returns `(time_t)(-1)`.
   * In most cases, `time(NULL)` works correctly and returns a valid timestamp. However, there are rare scenarios where it could fail (e.g., if the system cannot retrieve the current time).

From the time(2) Linux man-page

The tloc argument is obsolescent and should always be NULL in new code.
When tloc is NULL, the call cannot fail.

So, time(NULL) can't fail...

Maybe it's different on other systems...

Then there is

EOVERFLOW
The time cannot be represented as a time_t value. This can
happen if an executable with 32-bit time_t is run on a
64-bit kernel when the time is 2038-01-19 03:14:08 UTC or
later. However, when the system time is out of time_t
range in other situations, the behavior is undefined.

Meh...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants