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

Fix put_time() crash on invalid struct tm data #4883

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions stl/inc/xloctime
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,57 @@ _NODISCARD constexpr bool _Is_valid_strftime_specifier(const char _Specifier) {
return false;
}

_NODISCARD constexpr bool _Is_valid_strftime_tm_data(const tm* _Pt) {
TheStormN marked this conversation as resolved.
Show resolved Hide resolved
if (!_Pt) {
return false;
}

// seconds after the minute - [0, 60] including leap second
if (_Pt->tm_sec < 0 || _Pt->tm_sec > 60) {
return false;
}

// minutes after the hour - [0, 59]
if (_Pt->tm_min < 0 || _Pt->tm_min > 59) {
return false;
}

// hours since midnight - [0, 23]
if (_Pt->tm_hour < 0 || _Pt->tm_hour > 23) {
return false;
}

// day of the month - [1, 31]
if (_Pt->tm_mday < 1 || _Pt->tm_mday > 31) {
return false;
}

// months since January - [0, 11]
if (_Pt->tm_mon < 0 || _Pt->tm_mon > 11) {
return false;
}

// years since 1900 - UCRT max range is up until 8099
TheStormN marked this conversation as resolved.
Show resolved Hide resolved
if (_Pt->tm_year < -1900 || _Pt->tm_year > 8099) {
return false;
}

// days since Sunday - [0, 6]
if (_Pt->tm_wday < 0 || _Pt->tm_wday > 6) {
return false;
}

// days since January 1 - [0, 365]
if (_Pt->tm_yday < 0 || _Pt->tm_yday > 365) {
return false;
}

// daylight savings time flag
// _Pt->tm_isdst - no need from validation as UCRT treats it as boolean
TheStormN marked this conversation as resolved.
Show resolved Hide resolved

return true;
TheStormN marked this conversation as resolved.
Show resolved Hide resolved
}

_EXPORT_STD extern "C++" template <class _Elem, class _OutIt = ostreambuf_iterator<_Elem, char_traits<_Elem>>>
class time_put : public locale::facet { // facet for converting encoded times to text
public:
Expand All @@ -677,6 +728,12 @@ public:

_OutIt __CLR_OR_THIS_CALL put(_OutIt _Dest, ios_base& _Iosbase, _Elem _Fill, const tm* _Pt, const _Elem* _Fmtfirst,
const _Elem* _Fmtlast) const { // put formatted time from _Pt to _Dest for [_Fmtfirst, _Fmtlast)
if (!_Is_valid_strftime_tm_data(_Pt)) {
// supplied tm data is invalid, set failbit and return
_Iosbase.setstate(ios_base::failbit);
return _Dest;
}

const _Ctype& _Ctype_fac = _STD use_facet<_Ctype>(_Iosbase.getloc());

for (; _Fmtfirst != _Fmtlast; ++_Fmtfirst) {
Expand Down Expand Up @@ -812,6 +869,12 @@ public:

_OutIt __CLR_OR_THIS_CALL put(_OutIt _Dest, ios_base& _Iosbase, _Elem _Fill, const tm* _Pt, const _Elem* _Fmtfirst,
const _Elem* _Fmtlast) const { // put formatted time from _Pt to _Dest for [_Fmtfirst, _Fmtlast)
if (!_Is_valid_strftime_tm_data(_Pt)) {
// supplied tm data is invalid, set failbit and return
_Iosbase.setstate(ios_base::failbit);
return _Dest;
}

const _Ctype& _Ctype_fac = _STD use_facet<_Ctype>(_Iosbase.getloc());

for (; _Fmtfirst != _Fmtlast; ++_Fmtfirst) {
Expand Down Expand Up @@ -957,6 +1020,12 @@ public:

_OutIt __CLR_OR_THIS_CALL put(_OutIt _Dest, ios_base& _Iosbase, _Elem _Fill, const tm* _Pt, const _Elem* _Fmtfirst,
const _Elem* _Fmtlast) const { // put formatted time from _Pt to _Dest for [_Fmtfirst, _Fmtlast)
if (!_Is_valid_strftime_tm_data(_Pt)) {
// supplied tm data is invalid, set failbit and return
_Iosbase.setstate(ios_base::failbit);
return _Dest;
}

const _Ctype& _Ctype_fac = _STD use_facet<_Ctype>(_Iosbase.getloc());

for (; _Fmtfirst != _Fmtlast; ++_Fmtfirst) {
Expand Down
268 changes: 266 additions & 2 deletions tests/std/tests/Dev11_0836436_get_time/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ void test_buffer_resizing();
void test_gh_2618();
void test_gh_2848();
void test_gh_4820();
void test_gh_4882();

int main() {
assert(read_hour("12 AM") == 0);
Expand Down Expand Up @@ -159,6 +160,7 @@ int main() {
test_gh_2618();
test_gh_2848();
test_gh_4820();
test_gh_4882();
}

typedef istreambuf_iterator<char> Iter;
Expand Down Expand Up @@ -799,13 +801,13 @@ void test_invalid_argument() {
{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::badbit);
assert(wss.rdstate() == ios_base::failbit);
TheStormN marked this conversation as resolved.
Show resolved Hide resolved
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::badbit);
assert(ss.rdstate() == ios_base::failbit);
}
#endif // _M_CEE_PURE
}
Expand Down Expand Up @@ -944,3 +946,265 @@ void test_gh_4820() {
assert(wss.str() == L"\x043a%\x043e%\x0448%E\x043a%O\x0430");
}
}

void test_gh_4882() {
// GH-4882 <iomanip>: std::put_time should set failbit on invalid/out-of-range tm struct, instead of crash
TheStormN marked this conversation as resolved.
Show resolved Hide resolved
time_t t = time(nullptr);
tm currentTime;

// Check tm_sec - START
localtime_s(&currentTime, &t);
currentTime.tm_sec = -1; // set invalid lower limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}
TheStormN marked this conversation as resolved.
Show resolved Hide resolved

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}

localtime_s(&currentTime, &t);
currentTime.tm_sec = 61; // set invalid upper limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}
// Check tm_sec - END

// Check tm_min - START
localtime_s(&currentTime, &t);
currentTime.tm_min = -1; // set invalid lower limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}

localtime_s(&currentTime, &t);
currentTime.tm_min = 60; // set invalid upper limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}
// Check tm_min - END

// Check tm_hour - START
localtime_s(&currentTime, &t);
currentTime.tm_hour = -1; // set invalid lower limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}

localtime_s(&currentTime, &t);
currentTime.tm_hour = 24; // set invalid upper limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}
// Check tm_hour - END

// Check tm_mday - START
localtime_s(&currentTime, &t);
currentTime.tm_mday = 0; // set invalid lower limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}

localtime_s(&currentTime, &t);
currentTime.tm_mday = 32; // set invalid upper limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}
// Check tm_mday - END

// Check tm_mon - START
localtime_s(&currentTime, &t);
currentTime.tm_mon = -1; // set invalid lower limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}

localtime_s(&currentTime, &t);
currentTime.tm_mon = 12; // set invalid upper limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}
// Check tm_mon - END

// Check tm_year - START
localtime_s(&currentTime, &t);
currentTime.tm_year = -1901; // set invalid lower limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}

localtime_s(&currentTime, &t);
currentTime.tm_year = 8100; // set invalid upper limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}
// Check tm_year - END

// Check tm_wday - START
localtime_s(&currentTime, &t);
currentTime.tm_wday = -1; // set invalid lower limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}

localtime_s(&currentTime, &t);
currentTime.tm_wday = 7; // set invalid upper limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}
// Check tm_wday - END

// Check tm_yday - START
localtime_s(&currentTime, &t);
currentTime.tm_yday = -1; // set invalid lower limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}

localtime_s(&currentTime, &t);
currentTime.tm_yday = 366; // set invalid upper limit

{
wstringstream wss;
wss << put_time(&currentTime, L"%Y-%m-%d-%H-%M");
assert(wss.rdstate() == ios_base::failbit);
}

{
stringstream ss;
ss << put_time(&currentTime, "%Y-%m-%d-%H-%M");
assert(ss.rdstate() == ios_base::failbit);
}
// Check tm_yday - END
}
Loading