aboutsummaryrefslogtreecommitdiff
path: root/localtime.c
diff options
context:
space:
mode:
Diffstat (limited to 'localtime.c')
-rw-r--r--localtime.c75
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;
}