Skip to content

Security: validity datetime parsed as local time in PermissionsParser shifts credential lifecycle by host TZ/DST #6403

Description

@CrunchyJohnHaven

Hi eProsima team — flagging a structural issue in PermissionsParser::parse_validity where credential validity datetimes are parsed as the server's local time, which makes the effective validity window depend on the server's TZ (and DST state). Happy to follow up with a PR; the fix is small.

Where

  • src/cpp/security/accesscontrol/PermissionsParser.cpp:342validity.not_before = std::mktime(&time);
  • src/cpp/security/accesscontrol/PermissionsParser.cpp:363validity.not_after = std::mktime(&time);

Why this is a bug

std::mktime(&tm) interprets the struct tm as local time of the host, returning a time_t in seconds since the Unix epoch. The parsed value therefore depends on:

  1. The host's TZ environment / system timezone configuration
  2. Whether DST is in effect for that date (because the memset zeroes tm_isdst, behavior is implementation-defined; on many libc's, DST correction still applies during DST periods)

These values are then compared against current_time (a UTC-relative time_t) in Permissions.cpp:

  • Permissions.cpp:265: if (std::difftime(current_time, validity.not_before) >= 0)
  • Permissions.cpp:267: if (std::difftime(validity.not_after, current_time) >= 0)

So the access-control decision is comparing a UTC current_time against a local-time-shifted not_before / not_after. The window is offset by the host TZ (and DST). For example, an identical <not_after>2030-01-01T00:00:00</not_after> in a permissions file results in:

  • ~8 hours longer validity on a host in America/Los_Angeles (UTC-8 in winter)
  • ~8 hours shorter validity on a host in Asia/Singapore (UTC+8)
  • Off-by-one-hour around DST transitions on hosts in DST-observing zones

Why this matters

Permissions XML files are portable artifacts signed by a Permissions CA and intended to be distributed across the DDS domain. The CA cannot know the local timezone of every host that will consume the file, so an honest interpretation of CA intent is "UTC unless otherwise specified." Local-time parsing is non-portable and can shift effective credential validity by hours.

This isn't an immediate exploit so much as a credential-lifecycle correctness bug: a CA revoking a permission by issuing one with an earlier <not_after> may discover the revocation is silently delayed by the host's TZ offset, and vice versa.

Suggested fix

Use a UTC-based conversion path. On POSIX, timegm(3) is the symmetric counterpart to mktime that interprets the struct tm as UTC. On Windows, _mkgmtime is the equivalent. C++20 also has std::chrono::utc_clock and std::chrono::parse with explicit UTC handling.

#if defined(_WIN32)
    validity.not_before = _mkgmtime(&time);
#else
    validity.not_before = timegm(&time);
#endif

The same change in both call sites. Additionally, std::get_time(&time, "%Y-%m-%dT%T") does not consume an optional Z or ±HH:MM suffix; if the XML actually includes a timezone designator, it is silently ignored. Worth a docstring/comment clarifying the expected format.

Spec note

The OMG DDS-Security spec uses xsd:dateTime for validity fields. xsd:dateTime with no timezone designator is formally "timezone-unspecified," but security-relevant uses of xsd:dateTime are typically interpreted as UTC for portability reasons (see XMLDSig, SAML, etc.). The Fast-DDS test fixtures (and Cyclone DDS examples) use timezone-unspecified strings like 2015-09-15T01:00:00, which is exactly the case where this bug bites.

Happy to PR a timegm / _mkgmtime switch + tests in different TZs if that helps. Thanks for the project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions