backend: prevent timestamp overflow

When updating timestamps, make sure that the corresponding
microsecond value fits in a single signed 64-bit integer,
so that it won't result in an integer overflow, which is UB.

Also forbid timestamps before the Unix epoch, since working with
negative time_t is problematic.

This check does not apply to current timestamp, however,
add a check on backend startup to ensure sane system time.

There's no need to validate `tv_nsec`, since the kernel already
does that for us.
This commit is contained in:
CismonX 2025-03-20 12:46:11 +08:00
parent 09e186b348
commit 750c16077c
No known key found for this signature in database
GPG key ID: 3094873E29A482FB
3 changed files with 38 additions and 11 deletions

View file

@ -772,13 +772,8 @@ build_tsnode (
ts = &now;
}
// XXX: May overflow if system time is badly wrong,
// but don't bother to check.
time_t secs = ts->tv_sec + EPOCH_DIFF;
int64_t microsecs = secs * 1000000 + ts->tv_nsec / 1000;
if (microsecs < 0) {
microsecs = 0;
}
char buf[32];
int nbytes = snprintf(buf, sizeof(buf), "%" PRIi64, microsecs);
@ -1713,6 +1708,13 @@ static int
backend_init (
uint32_t flags
) {
struct timespec now;
xgetrealtime(&now);
if (!valid_ts_sec(now.tv_sec)) {
log_puts("bad system time");
return -1;
}
if (!(flags & BOOKMARKFS_BACKEND_LIB_READY)) {
if (0 != bookmarkfs_lib_init()) {
return -1;
@ -2517,10 +2519,16 @@ bookmark_set (
struct timespec const *times = val;
json_t *ts_node;
if (flags & BOOKMARK_FLAG(SET_ATIME)) {
if (!valid_ts_sec(times[0].tv_sec)) {
return -EINVAL;
}
build_tsnode(&times[0], &ts_node);
json_object_sset_new(node, "date_last_used", ts_node);
}
if (flags & BOOKMARK_FLAG(SET_MTIME)) {
if (!valid_ts_sec(times[1].tv_sec)) {
return -EINVAL;
}
build_tsnode(&times[1], &ts_node);
json_object_sset_new(node, "date_modified", ts_node);
}

View file

@ -2038,12 +2038,7 @@ static int64_t
timespec_to_usecs (
struct timespec const *ts
) {
int64_t microsecs = ts->tv_sec * 1000000 + ts->tv_nsec / 1000;
if (microsecs < 0) {
microsecs = 0;
}
return microsecs;
return ts->tv_sec * 1000000 + ts->tv_nsec / 1000;
}
static int
@ -3003,6 +2998,13 @@ static int
backend_init (
uint32_t flags
) {
struct timespec now;
xgetrealtime(&now);
if (!valid_ts_sec(now.tv_sec)) {
log_puts("bad system time");
return -1;
}
if (!(flags & BOOKMARKFS_BACKEND_LIB_READY)
&& !(flags & BOOKMARKFS_FRONTEND_MKFS)
) {
@ -3675,10 +3677,16 @@ bookmark_set (
if (flags & BOOKMARK_FLAG(SET_ATIME, SET_MTIME)) {
struct timespec const *times = val;
if (flags & BOOKMARK_FLAG(SET_ATIME)) {
if (!valid_ts_sec(times[0].tv_sec)) {
return -EINVAL;
}
place_cols.last_visit_date = timespec_to_usecs(&times[0]);
--xattr_id;
}
if (flags & BOOKMARK_FLAG(SET_MTIME)) {
if (!valid_ts_sec(times[1].tv_sec)) {
return -EINVAL;
}
bm_cols.last_modified = timespec_to_usecs(&times[1]);
}
} else {

View file

@ -57,6 +57,17 @@
#define FILENAME_BADLEN -2
#define FILENAME_DOTDOT -3
/**
* The maximum valid seconds that a signed 64-bit integer can hold
* when converted to microseconds.
*
* Equals to `INT64_MAX / 1000000 - EPOCH_DIFF - 1`:
* - See the Chromium backend for `EPOCH_DIFF`.
* - Considering `tv_nsec`, subtract another second.
*/
#define TIMESPEC_SEC_MAX INT64_C(9211727563253)
#define valid_ts_sec(sec) ((sec) >= 0 && (sec) <= TIMESPEC_SEC_MAX)
/**
* Opens the parent directory of a file, and stores its basename
* to `basename_ptr`.