Commit 5ea45f54 authored by Jonas 'Sortie' Termansen's avatar Jonas 'Sortie' Termansen Committed by Jan Kara
Browse files

isofs: fix Y2038 and Y2156 issues in Rock Ridge TF entry



This change implements the Rock Ridge TF entry LONG_FORM bit, which uses
the ISO 9660 17-byte date format (up to year 9999, with 10ms precision)
instead of the 7-byte date format (up to year 2155, with 1s precision).

Previously the LONG_FORM bit was ignored; and isofs would entirely
misinterpret the date as the wrong format, resulting in garbage
timestamps on the filesystem.

The Y2038 issue in iso_date() is fixed by returning a struct timespec64
instead of an int.

parse_rock_ridge_inode_internal() is fixed so it does proper bounds
checks of the TF entry timestamps.

Signed-off-by: default avatarJonas 'Sortie' Termansen <sortie@maxsi.org>
Signed-off-by: default avatarJan Kara <jack@suse.cz>
Link: https://patch.msgid.link/20250411145022.2292255-1-sortie@maxsi.org
parent 0405d4b6
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -1275,6 +1275,7 @@ static int isofs_read_inode(struct inode *inode, int relocated)
	unsigned long offset;
	struct iso_inode_info *ei = ISOFS_I(inode);
	int ret = -EIO;
	struct timespec64 ts;

	block = ei->i_iget5_block;
	bh = sb_bread(inode->i_sb, block);
@@ -1387,8 +1388,10 @@ static int isofs_read_inode(struct inode *inode, int relocated)
			inode->i_ino, de->flags[-high_sierra]);
	}
#endif
	inode_set_mtime_to_ts(inode,
			      inode_set_atime_to_ts(inode, inode_set_ctime(inode, iso_date(de->date, high_sierra), 0)));
	ts = iso_date(de->date, high_sierra ? ISO_DATE_HIGH_SIERRA : 0);
	inode_set_ctime_to_ts(inode, ts);
	inode_set_atime_to_ts(inode, ts);
	inode_set_mtime_to_ts(inode, ts);

	ei->i_first_extent = (isonum_733(de->extent) +
			isonum_711(de->ext_attr_length));
+3 −1
Original line number Diff line number Diff line
@@ -106,7 +106,9 @@ static inline unsigned int isonum_733(u8 *p)
	/* Ignore bigendian datum due to broken mastering programs */
	return get_unaligned_le32(p);
}
extern int iso_date(u8 *, int);
#define ISO_DATE_HIGH_SIERRA (1 << 0)
#define ISO_DATE_LONG_FORM (1 << 1)
struct timespec64 iso_date(u8 *p, int flags);

struct inode;		/* To make gcc happy */

+23 −17
Original line number Diff line number Diff line
@@ -412,7 +412,12 @@ parse_rock_ridge_inode_internal(struct iso_directory_record *de,
				}
			}
			break;
		case SIG('T', 'F'):
		case SIG('T', 'F'): {
			int flags, size, slen;

			flags = rr->u.TF.flags & TF_LONG_FORM ? ISO_DATE_LONG_FORM : 0;
			size = rr->u.TF.flags & TF_LONG_FORM ? 17 : 7;
			slen = rr->len - 5;
			/*
			 * Some RRIP writers incorrectly place ctime in the
			 * TF_CREATE field. Try to handle this correctly for
@@ -420,27 +425,28 @@ parse_rock_ridge_inode_internal(struct iso_directory_record *de,
			 */
			/* Rock ridge never appears on a High Sierra disk */
			cnt = 0;
			if (rr->u.TF.flags & TF_CREATE) {
				inode_set_ctime(inode,
						iso_date(rr->u.TF.times[cnt++].time, 0),
						0);
			if ((rr->u.TF.flags & TF_CREATE) && size <= slen) {
				inode_set_ctime_to_ts(inode,
						iso_date(rr->u.TF.data + size * cnt++, flags));
				slen -= size;
			}
			if (rr->u.TF.flags & TF_MODIFY) {
				inode_set_mtime(inode,
						iso_date(rr->u.TF.times[cnt++].time, 0),
						0);
			if ((rr->u.TF.flags & TF_MODIFY) && size <= slen) {
				inode_set_mtime_to_ts(inode,
						iso_date(rr->u.TF.data + size * cnt++, flags));
				slen -= size;
			}
			if (rr->u.TF.flags & TF_ACCESS) {
				inode_set_atime(inode,
						iso_date(rr->u.TF.times[cnt++].time, 0),
						0);
			if ((rr->u.TF.flags & TF_ACCESS) && size <= slen) {
				inode_set_atime_to_ts(inode,
						iso_date(rr->u.TF.data + size * cnt++, flags));
				slen -= size;
			}
			if (rr->u.TF.flags & TF_ATTRIBUTES) {
				inode_set_ctime(inode,
						iso_date(rr->u.TF.times[cnt++].time, 0),
						0);
			if ((rr->u.TF.flags & TF_ATTRIBUTES) && size <= slen) {
				inode_set_ctime_to_ts(inode,
						iso_date(rr->u.TF.data + size * cnt++, flags));
				slen -= size;
			}
			break;
		}
		case SIG('S', 'L'):
			{
				int slen;
+1 −5
Original line number Diff line number Diff line
@@ -65,13 +65,9 @@ struct RR_PL_s {
	__u8 location[8];
};

struct stamp {
	__u8 time[7];		/* actually 6 unsigned, 1 signed */
} __attribute__ ((packed));

struct RR_TF_s {
	__u8 flags;
	struct stamp times[];	/* Variable number of these beasts */
	__u8 data[];
} __attribute__ ((packed));

/* Linux-specific extension for transparent decompression */
+32 −17
Original line number Diff line number Diff line
@@ -16,24 +16,39 @@
 * to GMT.  Thus  we should always be correct.
 */

int iso_date(u8 *p, int flag)
struct timespec64 iso_date(u8 *p, int flags)
{
	int year, month, day, hour, minute, second, tz;
	int crtime;
	struct timespec64 ts;

	if (flags & ISO_DATE_LONG_FORM) {
		year = (p[0] - '0') * 1000 +
		       (p[1] - '0') * 100 +
		       (p[2] - '0') * 10 +
		       (p[3] - '0') - 1900;
		month = ((p[4] - '0') * 10 + (p[5] - '0'));
		day = ((p[6] - '0') * 10 + (p[7] - '0'));
		hour = ((p[8] - '0') * 10 + (p[9] - '0'));
		minute = ((p[10] - '0') * 10 + (p[11] - '0'));
		second = ((p[12] - '0') * 10 + (p[13] - '0'));
		ts.tv_nsec = ((p[14] - '0') * 10 + (p[15] - '0')) * 10000000;
		tz = p[16];
	} else {
		year = p[0];
		month = p[1];
		day = p[2];
		hour = p[3];
		minute = p[4];
		second = p[5];
	if (flag == 0) tz = p[6]; /* High sierra has no time zone */
	else tz = 0;
		ts.tv_nsec = 0;
		/* High sierra has no time zone */
		tz = flags & ISO_DATE_HIGH_SIERRA ? 0 : p[6];
	}

	if (year < 0) {
		crtime = 0;
		ts.tv_sec = 0;
	} else {
		crtime = mktime64(year+1900, month, day, hour, minute, second);
		ts.tv_sec = mktime64(year+1900, month, day, hour, minute, second);

		/* sign extend */
		if (tz & 0x80)
@@ -65,7 +80,7 @@ int iso_date(u8 *p, int flag)
		 * for pointing out the sign error.
		 */
		if (-52 <= tz && tz <= 52)
			crtime -= tz * 15 * 60;
			ts.tv_sec -= tz * 15 * 60;
	}
	return crtime;
	return ts;
}