From 10013d1e089eec2933944cdcd1bcfdc71e2ad8e2 Mon Sep 17 00:00:00 2001 From: orezraey Date: Thu, 22 Oct 2026 20:53:07 -0300 Subject: [PATCH] fix(libSceRtc): range-check the precise RFC 3339 formatters as measured on a PS5 Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceRtc/Export.cpp | 15 +++++++++++---- core/libs/tests/GuestRtc.cpp | 30 ++++++++++++++++++++++++++++++ docs/dev/debt/libSceRtc.md | 2 +- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index 3cc3b045de..169f77d826 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -759,9 +759,17 @@ int APS5_VABI sceRtcFormatRFC3339Precise(char* date_time, const RtcTick* utc, in utc = &now; } if (time_zone_minutes < -1439 || time_zone_minutes > 1439 || precision < 0 || precision > 6) return SCE_RTC_ERROR_INVALID_VALUE; - RtcTick local{}; - if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; - const RtcDateTime time = fromTick(local.tick); + const std::uint64_t shift = static_cast(time_zone_minutes < 0 ? -time_zone_minutes : time_zone_minutes) * TICKS_PER_MINUTE; + std::uint64_t localTick = 0; + if (time_zone_minutes < 0) { + if (utc->tick < shift) return SCE_RTC_ERROR_INVALID_VALUE; + localTick = utc->tick - shift; + } else { + if (utc->tick > MAX_TICK || MAX_TICK - utc->tick < shift) return SCE_RTC_ERROR_INVALID_YEAR; + localTick = utc->tick + shift; + } + if (localTick > MAX_TICK) return SCE_RTC_ERROR_INVALID_YEAR; + const RtcDateTime time = fromTick(localTick); int written = std::snprintf(date_time, 20, "%04u-%02u-%02uT%02u:%02u:%02u", time.year, time.month, time.day, time.hour, time.minute, time.second); if (precision != 0) { std::uint32_t divisor = 1; @@ -785,7 +793,6 @@ int APS5_VABI sceRtcFormatRFC3339PreciseLocalTime(char* date_time, const RtcTick utc = &now; } if (precision < 0 || precision > 6) return SCE_RTC_ERROR_INVALID_VALUE; - if (utc->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; return sceRtcFormatRFC3339Precise(date_time, utc, localOffsetMinutes(*utc), precision); } diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index 13d1678125..2a3580a918 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -883,6 +883,36 @@ int main() { Require(sceRtcFormatRFC3339PreciseLocalTime(precise, &first, 2) == 0 && std::strcmp(precise, "2024-02-29T12:34:56.78Z") == 0); Require(sceRtcFormatRFC3339PreciseLocalTime(precise, &first, 7) == invalidValue); Require(sceRtcFormatRFC3339PreciseLocalTime(nullptr, &first, 0) == invalidPointer); + constexpr int invalidYear = static_cast(0x80B50008u); + const RtcTick measured{63926543109123456ull}; + static constexpr const char* fractions[] = {"2026-10-02T13:05:09Z", "2026-10-02T13:05:09.1Z", "2026-10-02T13:05:09.12Z", "2026-10-02T13:05:09.123Z", + "2026-10-02T13:05:09.1234Z", "2026-10-02T13:05:09.12345Z", "2026-10-02T13:05:09.123456Z"}; + for (int digits = 0; digits <= 6; ++digits) + Require(sceRtcFormatRFC3339Precise(precise, &measured, 0, digits) == 0 && std::strcmp(precise, fractions[digits]) == 0); + struct Zone { + int minutes; + const char* expected; + }; + static constexpr Zone zones[] = {{60, "2026-10-02T14:05:09.123+01:00"}, {-180, "2026-10-02T10:05:09.123-03:00"}, {330, "2026-10-02T18:35:09.123+05:30"}, + {-330, "2026-10-02T07:35:09.123-05:30"}, {1439, "2026-10-03T13:04:09.123+23:59"}, {-1439, "2026-10-01T13:06:09.123-23:59"}, + {1, "2026-10-02T13:06:09.123+00:01"}, {-1, "2026-10-02T13:04:09.123-00:01"}, {720, "2026-10-03T01:05:09.123+12:00"}}; + for (const auto& zone : zones) + Require(sceRtcFormatRFC3339Precise(precise, &measured, zone.minutes, 3) == 0 && std::strcmp(precise, zone.expected) == 0); + const RtcTick yearEnd{63934358400999999ull}; + Require(sceRtcFormatRFC3339Precise(precise, &yearEnd, 1, 6) == 0 && std::strcmp(precise, "2027-01-01T00:01:00.999999+00:01") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &yearEnd, -1439, 2) == 0 && std::strcmp(precise, "2026-12-31T00:01:00.99-23:59") == 0); + const RtcTick zero{0}, maximum{315537897599999999ull}, pastMaximum{315537897600000000ull}, huge{~0ull}, nines{63926543109999999ull}; + Require(sceRtcFormatRFC3339Precise(precise, &zero, 0, 6) == 0 && std::strcmp(precise, "0001-01-01T00:00:00.000000Z") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &maximum, 0, 6) == 0 && std::strcmp(precise, "9999-12-31T23:59:59.999999Z") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &nines, 0, 1) == 0 && std::strcmp(precise, "2026-10-02T13:05:09.9Z") == 0); + std::memset(precise, '#', sizeof(precise)); + Require(sceRtcFormatRFC3339Precise(precise, &zero, -60, 0) == invalidValue && precise[0] == '#'); + Require(sceRtcFormatRFC3339Precise(precise, &maximum, 60, 6) == invalidYear && precise[0] == '#'); + Require(sceRtcFormatRFC3339Precise(precise, &pastMaximum, 0, 6) == invalidYear && precise[0] == '#'); + Require(sceRtcFormatRFC3339Precise(precise, &huge, 0, 6) == invalidYear && precise[0] == '#'); + Require(sceRtcFormatRFC3339Precise(precise, &pastMaximum, -60, 3) == 0 && std::strcmp(precise, "9999-12-31T23:00:00.000-01:00") == 0); + Require(sceRtcFormatRFC3339Precise(nullptr, &measured, 0, 9) == invalidPointer); + Require(sceRtcFormatRFC3339PreciseLocalTime(precise, &pastMaximum, 3) == invalidYear); Require(sceRtcCompareTick(&first, &second) == -1 && sceRtcCompareTick(&second, &first) == 1 && sceRtcCompareTick(&first, &first) == 0); Require(sceRtcCompareTick(nullptr, &first) == invalidPointer && sceRtcCompareTick(&first, nullptr) == invalidPointer); RtcTick current{}; diff --git a/docs/dev/debt/libSceRtc.md b/docs/dev/debt/libSceRtc.md index 56985f25a2..0ede16d7a8 100644 --- a/docs/dev/debt/libSceRtc.md +++ b/docs/dev/debt/libSceRtc.md @@ -12,7 +12,7 @@ - [sceRtcGetCurrentRawNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console reads clock 0x13 and returns its time plus the Unix epoch in ticks, or `0x80B50001` when the clock is unsupported (errno 5). The raw network clock is not modelled; this returns the host clock. Read from the disassembly of libSceRtc.sprx on firmware 13.60, not measured on a console - [sceRtcGetCurrentAdNetworkTick, sceRtcGetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console reads clocks 0x12 and 0x11 and falls back to network clock 0x10 when either clock is unsupported. These getters use their process-local clock when it was set, otherwise they follow the process-local network clock. The firmware's unsupported-clock result is not modelled. Read from the disassembly of libSceRtc.sprx on firmware 13.60, not measured on a console - [sceRtcSetCurrentTick, sceRtcSetCurrentNetworkTick, sceRtcSetCurrentAdNetworkTick, sceRtcSetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - a valid tick sets a process-local clock that advances from a monotonic host clock; the host system clock is not changed. The console sets clock 0, 0x10, 0x12 or 0x11. Null pointers return `0x80B50002` and ticks before the Unix epoch return `0x80B50003`, from disassembly of libSceRtc.sprx on firmware 13.60, not measured on a console. sceRtcSetConf passes its third and fourth arguments to the same clock call with clock 0 and throws; what the pair selects is unknown -- [sceRtcFormatRFC3339Precise, sceRtcFormatRFC3339PreciseLocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - read from the disassembly of libSceRtc.sprx on firmware 13.60: a null tick means the current tick, a precision above 6 or a zone outside -1439 to 1439 minutes returns `0x80B50003`, and the fraction is the microseconds truncated to the requested number of digits. Not compared with console output. The output buffer size is unknown; at most 33 bytes are written +- [sceRtcFormatRFC3339Precise, sceRtcFormatRFC3339PreciseLocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - measured in a payload process on a PS5 (firmware 13.60): a null tick means the current tick, a precision outside 0 to 6 or a zone outside -1439 to 1439 minutes returns `0x80B50003`, the fraction is the microseconds truncated to the requested digits, a local tick before 0001 returns `0x80B50003` and one after 9999 `0x80B50008`, and nothing is written on an error. The local-time variant takes the offset at that tick and checks only the local result, so a tick just past 9999 formats in a zone west of UTC. Here the offset comes from the host's local time, which on Windows has none for years after 3000, so such a tick formats as UTC there. At most 33 bytes are written; the output buffer size is unknown - [sceRtcGetYear, sceRtcGetMonth, sceRtcGetDay, sceRtcGetHour, sceRtcGetMinute, sceRtcGetSecond, sceRtcGetMicrosecond, sceRtcSetYear, sceRtcSetMonth, sceRtcSetDay, sceRtcSetHour, sceRtcSetMinute, sceRtcSetSecond, sceRtcSetMicrosecond](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - inferred component behavior without confirmed hardware trace. Component setters validate individual boundaries (e.g. day 1..31) independently without cross-field calendar validation; getters return `0x80B50002` (`SCE_RTC_ERROR_INVALID_POINTER`) for a null pointer ### Functional