Skip to content

Commit b6ea251

Browse files
committed
tzcode: Limit TZ for setugid programs
The zoneinfo parser can be told to read any file the program can access by setting TZ to either an absolute path, or a path relative to the zoneinfo directory. For setugid programs, we previously had a hack from OpenBSD which rejects values of TZ deemed unsafe, but that was rather arbitrary (anything containing a dot, for instance). Leverage openat() with AT_RESOLVE_BENEATH instead. For simplicity, move the TZ change detection code to after we've opened the file, and stat the file descriptor rather than the name. Reviewed by: jhb Differential Revision: https://reviews.freebsd.org/D52029
1 parent f5efc80 commit b6ea251

File tree

1 file changed

+45
-25
lines changed

1 file changed

+45
-25
lines changed

contrib/tzcode/localtime.c

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -408,13 +408,13 @@ scrub_abbrs(struct state *sp)
408408
* 1 if the time zone has changed
409409
*/
410410
static int
411-
tzfile_changed(const char *name)
411+
tzfile_changed(const char *name, int fd)
412412
{
413413
static char old_name[PATH_MAX];
414414
static struct stat old_sb;
415415
struct stat sb;
416416

417-
if (stat(name, &sb) != 0)
417+
if (_fstat(fd, &sb) != 0)
418418
return -1;
419419

420420
if (strcmp(name, old_name) != 0) {
@@ -446,8 +446,10 @@ union input_buffer {
446446
+ 4 * TZ_MAX_TIMES];
447447
};
448448

449+
#ifndef __FreeBSD__
449450
/* TZDIR with a trailing '/' rather than a trailing '\0'. */
450451
static char const tzdirslash[sizeof TZDIR] = TZDIR "/";
452+
#endif /* !__FreeBSD__ */
451453

452454
/* Local storage needed for 'tzloadbody'. */
453455
union local_storage {
@@ -460,13 +462,15 @@ union local_storage {
460462
struct state st;
461463
} u;
462464

465+
#ifndef __FreeBSD__
463466
/* The name of the file to be opened. Ideally this would have no
464467
size limits, to support arbitrarily long Zone names.
465468
Limiting Zone names to 1024 bytes should suffice for practical use.
466469
However, there is no need for this to be smaller than struct
467470
file_analysis as that struct is allocated anyway, as the other
468471
union member. */
469472
char fullname[max(sizeof(struct file_analysis), sizeof tzdirslash + 1024)];
473+
#endif /* !__FreeBSD__ */
470474
};
471475

472476
/* Load tz data from the file named NAME into *SP. Read extended
@@ -480,7 +484,11 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
480484
register int fid;
481485
register int stored;
482486
register ssize_t nread;
487+
#ifdef __FreeBSD__
488+
int serrno;
489+
#else /* !__FreeBSD__ */
483490
register bool doaccess;
491+
#endif /* !__FreeBSD__ */
484492
register union input_buffer *up = &lsp->u.u;
485493
register int tzheadsize = sizeof(struct tzhead);
486494

@@ -494,6 +502,7 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
494502

495503
if (name[0] == ':')
496504
++name;
505+
#ifndef __FreeBSD__
497506
#ifdef SUPPRESS_TZDIR
498507
/* Do not prepend TZDIR. This is intended for specialized
499508
applications only, due to its security implications. */
@@ -502,9 +511,7 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
502511
doaccess = name[0] == '/';
503512
#endif
504513
if (!doaccess) {
505-
#ifndef __FreeBSD__
506514
char const *dot;
507-
#endif /* !__FreeBSD__ */
508515
if (sizeof lsp->fullname - sizeof tzdirslash <= strlen(name))
509516
return ENAMETOOLONG;
510517

@@ -514,7 +521,6 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
514521
memcpy(lsp->fullname, tzdirslash, sizeof tzdirslash);
515522
strcpy(lsp->fullname + sizeof tzdirslash, name);
516523

517-
#ifndef __FreeBSD__
518524
/* Set doaccess if NAME contains a ".." file name
519525
component, as such a name could read a file outside
520526
the TZDIR virtual subtree. */
@@ -524,35 +530,49 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
524530
doaccess = true;
525531
break;
526532
}
527-
#endif /* !__FreeBSD__ */
528533

529534
name = lsp->fullname;
530535
}
531-
#ifndef __FreeBSD__
532536
if (doaccess && access(name, R_OK) != 0)
533537
return errno;
534-
#endif /* !__FreeBSD__ */
535-
#ifdef DETECT_TZ_CHANGES
536-
if (doextend) {
537-
/*
538-
* Detect if the timezone file has changed. Check
539-
* 'doextend' to ignore TZDEFRULES; the tzfile_changed()
540-
* function can only keep state for a single file.
541-
*/
542-
switch (tzfile_changed(name)) {
543-
case -1:
544-
return errno;
545-
case 0:
546-
return 0;
547-
case 1:
548-
break;
549-
}
550-
}
551-
#endif /* DETECT_TZ_CHANGES */
538+
#else /* __FreeBSD__ */
539+
if (issetugid()) {
540+
const char *relname = name;
541+
if (strncmp(relname, TZDIR "/", strlen(TZDIR) + 1) == 0)
542+
relname += strlen(TZDIR) + 1;
543+
int dd = _open(TZDIR, O_DIRECTORY | O_RDONLY);
544+
if (dd < 0)
545+
return errno;
546+
fid = _openat(dd, relname, O_RDONLY | O_BINARY, AT_RESOLVE_BENEATH);
547+
serrno = errno;
548+
_close(dd);
549+
errno = serrno;
550+
} else
551+
#endif
552552
fid = _open(name, O_RDONLY | O_BINARY);
553553
if (fid < 0)
554554
return errno;
555555

556+
#ifdef DETECT_TZ_CHANGES
557+
if (doextend) {
558+
/*
559+
* Detect if the timezone file has changed. Check 'doextend' to
560+
* ignore TZDEFRULES; the tzfile_changed() function can only
561+
* keep state for a single file.
562+
*/
563+
switch (tzfile_changed(name, fid)) {
564+
case -1:
565+
serrno = errno;
566+
_close(fid);
567+
return serrno;
568+
case 0:
569+
_close(fid);
570+
return 0;
571+
case 1:
572+
break;
573+
}
574+
}
575+
#endif /* DETECT_TZ_CHANGES */
556576
nread = _read(fid, up->buf, sizeof up->buf);
557577
if (nread < tzheadsize) {
558578
int err = nread < 0 ? errno : EINVAL;

0 commit comments

Comments
 (0)