Skip to content

[Bug] [libc] settimeofday 在 time64/long32 配置下窄化校验,错误拒绝有效时间戳 #11922

Open

Description

@dongly

RT-Thread Version

master, commit a59e72fb5c2f272761a2813fc9bd9fe18eb29d7d.

Affected area

libc / RTC, components/libc/compilers/common/ctime.c, settimeofday().

Hardware / Architecture / Toolchain

32 位 ARM ABI:time_t 为 64 位有符号类型,long 为 32 位。工具链为 GNU Arm Embedded Toolchain 10.3-2021.10(newlib)。已通过隔离的 ARM 编译验证类型大小及窄化转换行为;尚未验证已部署固件,不代表已经完成实机测试。

问题描述 / Describe the bug

settimeofday() 在验证时间戳时先强制转换为 long:

if (tv != RT_NULL && (long)tv->tv_usec >= 0 && (long)tv->tv_sec >= 0)

在 time64/long32 配置下,有效的正时间戳可能因此变成负数,在调用 RTC 驱动之前就返回 -1、设置 errno=EINVAL。

固定版本源码 L559–599

复现 / Reproduction

在上述 ABI 下启用 RT_USING_RTC,并提供能接受该时间的 RTC 设备:

#include <time.h>
#include <sys/time.h>
#include <errno.h>

_Static_assert(sizeof(time_t) == 8, "requires time64");
_Static_assert(sizeof(long) == 4, "requires long32");

struct timeval tv = {0};
tv.tv_sec = (time_t)2208988800LL; /* 2040-01-01 00:00:00 UTC */
tv.tv_usec = 0;
int rc = settimeofday(&tv, NULL);

预期:该有效正时间应通过入口校验,由 RTC 驱动决定是否支持设置。

源码推导结果:测试所用 ARM GCC 的 (long)2208988800LL 为 -2085978496,入口条件为假,因此两个 RTC control 操作均不会被调用,返回 -1/EINVAL。

验证边界:已隔离编译确认类型大小及强转后的判断结果,尚未在目标板上端到端执行上面的调用。超范围有符号转换的结果取决于实现;这里的数值针对已验证的 GCC 行为。并非所有 2038 年之后的时间都会被拒绝:在该行为下首个拒绝区间为秒值 2147483648..4294967295。

Suggested direction

避免为了检查符号而将 tv_sec 窄化为 long,按字段自身的类型进行范围验证。该问题不限于 picolibc,time64/long32 的 newlib 配置同样可能受影响。它与 time32 本身的表示范围限制是两个独立问题。

引入提交和版本 / Regression history

  • 引入该窄化检查的提交:92859cc79e,2023-08-07,[libc][time] silence the incorrect type warning。
  • 该提交将原先直接比较字段的表达式改成 (long) 强转;首个包含它的正式 release 为 v5.0.2。
  • 这是条件性回归:只有字段类型与 long 的宽度/表示范围不匹配并输入对应时间戳时才暴露,并非从该版本开始所有平台都不能设置时间。

其他实现参考 / Comparison

这里只比较有效正时间戳的传递方式,不主张所有 OS 的设置时间权限、负值策略或硬件支持范围相同。

Activity

  1. github-actions commented on Oct 23, 2026

    @github-actions

    如需认领并处理此 Issue,请评论 /claim。

    To claim and work on this issue, comment /claim.

  2. Yunlk commented on Oct 24, 2026

    @Yunlk
    Contributor

    /claim

  3. github-actions commented on Oct 24, 2026

    @github-actions

    @Yunlk has claimed this issue.

  4. Yunlk commented on Oct 24, 2026

    @Yunlk
    Contributor

    在 QEMU 上实测复现了,也验证了改法。边界和你说的完全一致。

    环境

    • bsp/qemu-vexpress-a9(32 位 ARM,RT_USING_RTC=y,板上有 rtc 设备)
    • arm-none-eabi-gcc 15.2.1,QEMU 11.0.0,master 1c801efa
    • 运行时确认:sizeof(time_t)=8、sizeof(long)=4 —— 正是 time64/long32

    复现(未修复)

    /* ctime.c:567 */
    if (tv != RT_NULL && (long)tv->tv_usec >= 0 && (long)tv->tv_sec >= 0)

    逐点扫了一遍边界:

    tv_sec 对应时间 修复前 修复后
    0 1970-01-01 ret=0 ret=0
    2147483647 2038-01-19 ret=0 ret=0
    2147483648 2038+1 ret=-1, errno=22 (EINVAL) ret=0
    2208988800 2040-01-01(你举的例子) ret=-1, errno=22 ret=0
    4294967295 2106-02-07 ret=-1, errno=22 ret=0

    首个被拒值正好是 2147483648,与 issue 里写的区间一致;INT32_MAX 及以下不受影响。

    改法

    去掉强转,按字段自身类型校验:

     #ifdef RT_USING_RTC
    -    if (tv != RT_NULL && (long)tv->tv_usec >= 0 && (long)tv->tv_sec >= 0)
    +    /*
    +     * Check the fields by their own type. Casting tv_sec to long narrows it on
    +     * ILP32 targets where time_t is 64 bit (time64/long32): every timestamp
    +     * above INT32_MAX turned negative and was rejected with EINVAL, even though
    +     * it is a perfectly valid value for time_t.
    +     */
    +    if (tv != RT_NULL && tv->tv_usec >= 0 && tv->tv_sec >= 0)
         {

    改完同一份固件重跑,五个点全部 ret=0,INT32_MAX 以下行为未变。

    两点补充

    1. tv_usec 的强转也一并去掉了,但它在 ILP32 上本来就没问题(suseconds_t 32 位);保留只是为了和 tv_sec 写法统一。如果你希望改动更小,只动 tv_sec 也可以。

    2. 顺带发现但未改动:这里只校验了 tv_usec >= 0,没有校验 POSIX 要求的 < 1000000。传 tv_usec = 5000000 目前会被放行并送进 RTC 驱动。这属于另一处校验缺失,是否一并收紧由维护者判断,我没有把它混进这次的改动。

    关于 utest

    这个用例要能跑,前提是系统里有 rtc 设备(_control_rtc() 走 rt_device_find("rtc"),找不到会直接返回 -RT_ENOSYS,和强转问题是两条不同的失败路径)。CI 里 qemu-vexpress-a9 有 rtc,所以理论上可以加;但需要确认其他跑 utest 的 BSP 是否都有 rtc 设备,否则用例会因为找不到设备而误失败。

    如果需要,我可以把补丁提成 PR,并附一条在 -DRT_USING_RTC 下条件编译的 utest(断言 INT32_MAX + 1 与 2208988800 两个值能通过)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions