Skip to content

Ido*sqlConnection#FieldToEscapedString(): don't write out of range time #10058

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

Merged
merged 2 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 6 additions & 2 deletions lib/db_ido_mysql/idomysqlconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -891,9 +891,13 @@ bool IdoMysqlConnection::FieldToEscapedString(const String& key, const Value& va

*result = static_cast<long>(dbrefcol);
} else if (DbValue::IsTimestamp(value)) {
long ts = rawvalue;
// MySQL TIMESTAMP columns have the year-2038 problem, hence the upper limit below.
// Also, they don't accept FROM_UNIXTIME(0): ERROR 1292 (22007): Incorrect datetime value: '1970-01-01 00:00:00'

double ts = rawvalue;
std::ostringstream msgbuf;
msgbuf << "FROM_UNIXTIME(" << ts << ")";
msgbuf << "FROM_UNIXTIME(" << std::fixed << std::setprecision(0)
<< std::fmin(std::fmax(ts, 1.0), 2147483647.0) << ")";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't using std::clamp make this more readable? Same goes for the other change at PostgreSQL?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if we all agree not to backport this to v2.13 which doesn't support C++17.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seriously, std::clamp shouldn't be a deal breaker for not back porting it. So, to me this should also be fine!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't using std::clamp make this more readable?

Depends, my understand was that fmin/fmax was used instead of the regular min/max for handling the dangers of floating point (otherwise, NaN might give you undefined behavior, C++ is fun).

Only if we all agree not to backport this to v2.13 which doesn't support C++17.

General remark on that: one option for such situations would be to make a dedicated commit for adapting the code to make use of newer C++ features that could then just be omitted when backporting.

*result = Value(msgbuf.str());
} else if (DbValue::IsObjectInsertID(value)) {
auto id = static_cast<long>(rawvalue);
Expand Down
45 changes: 43 additions & 2 deletions lib/db_ido_pgsql/idopgsqlconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "base/context.hpp"
#include "base/statsfunction.hpp"
#include "base/defer.hpp"
#include <cmath>
#include <utility>

using namespace icinga;
Expand Down Expand Up @@ -700,9 +701,49 @@ bool IdoPgsqlConnection::FieldToEscapedString(const String& key, const Value& va

*result = static_cast<long>(dbrefcol);
} else if (DbValue::IsTimestamp(value)) {
long ts = rawvalue;
// In addition to the limits of PostgreSQL itself (4713BC - 294276AD),
// years not fitting in YYYY may cause problems, see e.g. https://github.com/golang/go/issues/4556.
// RFC 3339: "All dates and times are assumed to be (...) somewhere between 0000AD and 9999AD."
// The below limits include safety buffers to make sure the timestamps are within 0-9999 AD in all time zones:
//
// postgres=# \x
// Expanded display is on.
// postgres=# SELECT TO_TIMESTAMP(-62135510400) AT TIME ZONE 'UTC' AS utc,
// postgres-# TO_TIMESTAMP(-62135510400) AT TIME ZONE 'Asia/Vladivostok' AS east,
// postgres-# TO_TIMESTAMP(-62135510400) AT TIME ZONE 'America/Juneau' AS west,
// postgres-# TO_TIMESTAMP(-62135510400) AT TIME ZONE 'America/Nuuk' AS north;
// -[ RECORD 1 ]--------------
// utc | 0001-01-02 00:00:00
// east | 0001-01-02 08:47:31
// west | 0001-01-02 15:02:19
// north | 0001-01-01 20:33:04
//
// postgres=# SELECT TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'UTC' AS utc,
// postgres-# TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'Asia/Vladivostok' AS east,
// postgres-# TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'America/Juneau' AS west,
// postgres-# TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'America/Nuuk' AS north;
// -[ RECORD 1 ]-----------------
// utc | 0001-01-01 00:00:00
// east | 0001-01-01 08:47:31
// west | 0001-01-01 15:02:19
// north | 0001-12-31 20:33:04 BC
//
// postgres=# SELECT TO_TIMESTAMP(253402214400) AT TIME ZONE 'UTC' AS utc,
// postgres-# TO_TIMESTAMP(253402214400) AT TIME ZONE 'Asia/Vladivostok' AS east,
// postgres-# TO_TIMESTAMP(253402214400) AT TIME ZONE 'America/Juneau' AS west,
// postgres-# TO_TIMESTAMP(253402214400) AT TIME ZONE 'America/Nuuk' AS north;
// -[ RECORD 1 ]-------------
// utc | 9999-12-31 00:00:00
// east | 9999-12-31 10:00:00
// west | 9999-12-30 15:00:00
// north | 9999-12-30 22:00:00
//
// postgres=#

double ts = rawvalue;
std::ostringstream msgbuf;
msgbuf << "TO_TIMESTAMP(" << ts << ") AT TIME ZONE 'UTC'";
msgbuf << "TO_TIMESTAMP(" << std::fixed << std::setprecision(0)
<< std::fmin(std::fmax(ts, -62135510400.0), 253402214400.0) << ") AT TIME ZONE 'UTC'";
*result = Value(msgbuf.str());
} else if (DbValue::IsObjectInsertID(value)) {
auto id = static_cast<long>(rawvalue);
Expand Down
Loading