diff options
Diffstat (limited to 'localtime.c')
| -rw-r--r-- | localtime.c | 75 |
1 files changed, 44 insertions, 31 deletions
diff --git a/localtime.c b/localtime.c index 0091b81eba3d..be3fe1d5d8f4 100644 --- a/localtime.c +++ b/localtime.c @@ -227,7 +227,7 @@ struct timespec { time_t tv_sec; long tv_nsec; }; /* How many seconds to wait before checking the default TZif file again. Negative means no checking. Default to 61 if DETECT_TZ_CHANGES - (as circa 2025 FreeBSD builds its localtime.c with -DDETECT_TZ_CHANGES), + (as FreeBSD optionally builds its localtime.c with -DDETECT_TZ_CHANGES), and to -1 otherwise. */ #ifndef TZ_CHANGE_INTERVAL # ifdef DETECT_TZ_CHANGES @@ -288,10 +288,12 @@ typedef intmax_t timex_t; # endif #endif -/* Placeholders for platforms lacking openat. */ +/* Placeholders for platforms lacking AT_FCWD, openat, and fstatat. */ #ifndef AT_FDCWD # define AT_FDCWD (-1) /* any negative value will do */ static int openat(int dd, char const *path, int oflag) { unreachable (); } +static int fstatat(int dd, char const *path, struct stat *st, int flags) +{ unreachable(); } #endif /* Port to platforms that lack some O_* flags. Unless otherwise @@ -425,8 +427,7 @@ static char const *utc = etc_utc + sizeof "Etc/" - 1; approach of opening "/usr/share/zoneinfo/America/Los_Angeles". Although the OPENAT_TZDIR approach is less efficient, suffers from spurious EMFILE and ENFILE failures, and is no more secure in practice, - it is how bleeding edge FreeBSD did things from August 2025 - through at least September 2025. */ + bleeding edge FreeBSD started doing it this way in August 2025. */ #ifndef OPENAT_TZDIR # define OPENAT_TZDIR 0 #endif @@ -917,6 +918,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, int dd = AT_FDCWD; int oflags = (O_RDONLY | O_BINARY | O_CLOEXEC | O_CLOFORK | O_IGNORE_CTTY | O_NOCTTY | O_REGULAR); + bool might_escape = false; int err; struct stat st; st.st_ctime = 0; @@ -943,16 +945,8 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, continue; else if (issetugid()) return ENOTCAPABLE; - else if (!O_REGULAR) { - /* Check for devices, as their mere opening could have - unwanted side effects. Though racy, there is no - portable way to fix the races. This check is needed - only for files not otherwise known to be non-devices. */ - if (stat(name, &st) < 0) - return errno; - if (!S_ISREG(st.st_mode)) - return EINVAL; - } + else + might_escape = true; } if (relname[0] != '/') { @@ -964,8 +958,12 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, for (component = relname; component[0]; component++) if (component[0] == '.' && component[1] == '.' && component[2] == '/' - && (component == relname || component[-1] == '/')) - return ENOTCAPABLE; + && (component == relname || component[-1] == '/')) { + if (issetugid()) + return ENOTCAPABLE; + might_escape = true; + break; + } } if (OPENAT_TZDIR && !SUPPRESS_TZDIR) { @@ -978,7 +976,10 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, | O_BINARY | O_CLOEXEC | O_CLOFORK | O_DIRECTORY)); if (dd < 0) return errno; - oflags |= O_RESOLVE_BENEATH; + if (O_RESOLVE_BENEATH && issetugid()) { + oflags |= O_RESOLVE_BENEATH; + might_escape = false; + } } } @@ -1014,6 +1015,18 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, #endif } + /* For a platform that lacks O_REGULAR and a file that might + be outside TZDIR, check that it is a regular file, + as merely opening a device could have unwanted side effects. + Though racy, there is no portable way to fix the race. */ + if (!O_REGULAR && might_escape) { + /* (oflags & O_RESOLVE_BENEATH) must be zero here. */ + if ((OPENAT_TZDIR ? fstatat(dd, relname, &st, 0) : stat(name, &st)) + < 0) + return errno; + if (!S_ISREG(st.st_mode)) + return EFTYPE; + } fid = OPENAT_TZDIR ? openat(dd, relname, oflags) : open(name, oflags); err = errno; if (0 <= dd) @@ -1036,7 +1049,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, } up = &lsp->u.u; nread = read(fid, up->buf, sizeof up->buf); - err = tzheadsize <= nread ? 0 : nread < 0 ? errno : EINVAL; + err = tzheadsize <= nread ? 0 : nread < 0 ? errno : EFTYPE; } close(fid); if (err) @@ -1064,7 +1077,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, && 0 <= charcnt && charcnt <= TZ_MAX_CHARS && 0 <= ttisstdcnt && ttisstdcnt <= TZ_MAX_TYPES && 0 <= ttisutcnt && ttisutcnt <= TZ_MAX_TYPES)) - return EINVAL; + return EFTYPE; datablock_size = (timecnt * stored /* ats */ + timecnt /* types */ @@ -1074,12 +1087,12 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, + ttisstdcnt /* ttisstds */ + ttisutcnt); /* ttisuts */ if (nread < tzheadsize + datablock_size) - return EINVAL; + return EFTYPE; if (skip_datablock) p += datablock_size; else if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) && (ttisutcnt == typecnt || ttisutcnt == 0))) - return EINVAL; + return EFTYPE; else { int_fast64_t prevtr = -1; int_fast32_2s prevcorr; @@ -1102,7 +1115,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, ? TIME_T_MIN : at); if (timecnt && attime <= sp->ats[timecnt - 1]) { if (attime < sp->ats[timecnt - 1]) - return EINVAL; + return EFTYPE; sp->types[i - 1] = 0; timecnt--; } @@ -1115,7 +1128,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, for (i = 0; i < sp->timecnt; ++i) { unsigned char typ = *p++; if (sp->typecnt <= typ) - return EINVAL; + return EFTYPE; if (sp->types[i]) sp->types[timecnt++] = typ; } @@ -1129,18 +1142,18 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, cause trouble both in this file and in callers. Also, it violates RFC 9636 section 3.2. */ if (utoff < -TWO_31_MINUS_1) - return EINVAL; + return EFTYPE; ttisp = &sp->ttis[i]; ttisp->tt_utoff = utoff; p += 4; isdst = *p++; if (! (isdst < 2)) - return EINVAL; + return EFTYPE; ttisp->tt_isdst = isdst; desigidx = *p++; if (! (desigidx < sp->charcnt)) - return EINVAL; + return EFTYPE; ttisp->tt_desigidx = desigidx; } for (i = 0; i < sp->charcnt; ++i) @@ -1159,7 +1172,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, /* Leap seconds cannot occur before the Epoch, or out of order. */ if (tr <= prevtr) - return EINVAL; + return EFTYPE; /* To avoid other botches in this code, each leap second's correction must differ from the previous one's by 1 @@ -1171,7 +1184,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, ? corr == prevcorr + 1 : (corr == prevcorr || corr == prevcorr - 1)))) - return EINVAL; + return EFTYPE; prevtr = tr; prevcorr = corr; @@ -1193,7 +1206,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, ttisp->tt_ttisstd = false; else { if (*p != true && *p != false) - return EINVAL; + return EFTYPE; ttisp->tt_ttisstd = *p++; } } @@ -1205,7 +1218,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, ttisp->tt_ttisut = false; else { if (*p != true && *p != false) - return EINVAL; + return EFTYPE; ttisp->tt_ttisut = *p++; } } @@ -1290,7 +1303,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, } } if (sp->typecnt == 0) - return EINVAL; + return EFTYPE; return 0; } |
