From: Jonas 'Sortie' Termansen sortie@maxsi.org
[ Upstream commit 5ea45f54c8d6ca2a95b7bd450ee9eb253310bfd3 ]
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: Jonas 'Sortie' Termansen sortie@maxsi.org Signed-off-by: Jan Kara jack@suse.cz Link: https://patch.msgid.link/20250411145022.2292255-1-sortie@maxsi.org Signed-off-by: Sasha Levin sashal@kernel.org ---
**YES** This commit should be backported to stable kernel trees. Here's my detailed analysis: ## Analysis of the Commit ### 1. **Fixes Critical Date/Time Issues** The commit addresses **two major year rollover problems**: - **Y2038 Issue**: The old `iso_date()` function returned `int` (32-bit), limiting dates to January 19, 2038 - **Y2156 Issue**: Rock Ridge TF entries using 7-byte format are limited to year 2155 (255 + 1900) These are **fundamental correctness issues** that affect real users accessing CD/DVD filesystems. ### 2. **Concrete Bug Fixes** **Before the fix:** ```c int iso_date(u8 *p, int flag) // Returns 32-bit int - Y2038 problem ``` **After the fix:** ```c struct timespec64 iso_date(u8 *p, int flags) // Returns 64-bit timespec - Y2038 safe ``` **Key improvements:** - **LONG_FORM support**: Previously ignored `TF_LONG_FORM` bit, causing "garbage timestamps" - **Proper bounds checking**: Validates timestamp entry sizes before processing - **Extended date range**: 17-byte format supports years up to 9999 vs 2155 ### 3. **Meets Stable Tree Criteria** **✓ Important Bug Fix**: Fixes user-visible timestamp corruption **✓ Small and Contained**: Changes limited to isofs timestamp handling **✓ Low Regression Risk**: - Doesn't change filesystem on-disk format - Only affects timestamp interpretation, not filesystem structure - Maintains backward compatibility **✓ No Architectural Changes**: Internal timestamp processing only ### 4. **Critical Code Analysis** **fs/isofs/rock.c changes** show proper bounds checking: ```c // NEW: Proper validation before accessing timestamp data if ((rr->u.TF.flags & TF_CREATE) && size <= slen) { inode_set_ctime_to_ts(inode, iso_date(rr->u.TF.data + size capability_test capability_test.c f2fs_folio_analysis.md ipv4_multipath_analysis.md ipv6_route_allocation_rcu_analysis.md ixgbe_e610_set_phys_id_analysis.md linux lpfc_timeout_analysis.md mac80211_mlo_mbssid_analysis.md pfcp_driver_historical_analysis.md rtl_bb_delay_analysis.md rtw89_mlo_analysis.md tcp_multipath_load_balance_analysis.md test_unaligned_diff test_unaligned_diff.c type_size_check type_size_check.c veth_driver_analysis.md wifi_mlo_mbssid_tx_link_id_analysis.md cnt++, flags)); slen -= size; } ``` **fs/isofs/util.c changes** add long-form timestamp support: ```c if (flags & ISO_DATE_LONG_FORM) { // 17-byte format: YYYY MM DD HH MM SS with nanosecond precision year = (p[0] - '0') capability_test capability_test.c f2fs_folio_analysis.md ipv4_multipath_analysis.md ipv6_route_allocation_rcu_analysis.md ixgbe_e610_set_phys_id_analysis.md linux lpfc_timeout_analysis.md mac80211_mlo_mbssid_analysis.md pfcp_driver_historical_analysis.md rtl_bb_delay_analysis.md rtw89_mlo_analysis.md tcp_multipath_load_balance_analysis.md test_unaligned_diff test_unaligned_diff.c type_size_check type_size_check.c veth_driver_analysis.md wifi_mlo_mbssid_tx_link_id_analysis.md 1000 + (p[1] - '0') capability_test capability_test.c f2fs_folio_analysis.md ipv4_multipath_analysis.md ipv6_route_allocation_rcu_analysis.md ixgbe_e610_set_phys_id_analysis.md linux lpfc_timeout_analysis.md mac80211_mlo_mbssid_analysis.md pfcp_driver_historical_analysis.md rtl_bb_delay_analysis.md rtw89_mlo_analysis.md tcp_multipath_load_balance_analysis.md test_unaligned_diff test_unaligned_diff.c type_size_check type_size_check.c veth_driver_analysis.md wifi_mlo_mbssid_tx_link_id_analysis.md 100 + (p[2] - '0') capability_test capability_test.c f2fs_folio_analysis.md ipv4_multipath_analysis.md ipv6_route_allocation_rcu_analysis.md ixgbe_e610_set_phys_id_analysis.md linux lpfc_timeout_analysis.md mac80211_mlo_mbssid_analysis.md pfcp_driver_historical_analysis.md rtl_bb_delay_analysis.md rtw89_mlo_analysis.md tcp_multipath_load_balance_analysis.md test_unaligned_diff test_unaligned_diff.c type_size_check type_size_check.c veth_driver_analysis.md wifi_mlo_mbssid_tx_link_id_analysis.md 10 + (p[3] - '0') - 1900; // ... full precision parsing ts.tv_nsec = ((p[14] - '0') capability_test capability_test.c f2fs_folio_analysis.md ipv4_multipath_analysis.md ipv6_route_allocation_rcu_analysis.md ixgbe_e610_set_phys_id_analysis.md linux lpfc_timeout_analysis.md mac80211_mlo_mbssid_analysis.md pfcp_driver_historical_analysis.md rtl_bb_delay_analysis.md rtw89_mlo_analysis.md tcp_multipath_load_balance_analysis.md test_unaligned_diff test_unaligned_diff.c type_size_check type_size_check.c veth_driver_analysis.md wifi_mlo_mbssid_tx_link_id_analysis.md 10 + (p[15] - '0')) capability_test capability_test.c f2fs_folio_analysis.md ipv4_multipath_analysis.md ipv6_route_allocation_rcu_analysis.md ixgbe_e610_set_phys_id_analysis.md linux lpfc_timeout_analysis.md mac80211_mlo_mbssid_analysis.md pfcp_driver_historical_analysis.md rtl_bb_delay_analysis.md rtw89_mlo_analysis.md tcp_multipath_load_balance_analysis.md test_unaligned_diff test_unaligned_diff.c type_size_check type_size_check.c veth_driver_analysis.md wifi_mlo_mbssid_tx_link_id_analysis.md 10000000; } ``` ### 5. **Comparison with Historical Examples** This commit closely matches **Similar Commit #1** (marked YES), which also: - Fixed isofs timestamp range issues - Used small, targeted changes - Included explicit stable tree mention - Changed data types for Y2038 compatibility ### 6. **Real-World Impact** Users mounting CD/DVD images with: - Timestamps after 2038 (increasingly common) - Rock Ridge extensions using long-form timestamps - Files created after 2155 with 7-byte timestamps Currently experience **corrupted or garbage timestamps**, which this commit fixes. ### 7. **Risk Assessment** **Very Low Risk:** - Changes internal timestamp parsing only - Maintains compatibility with existing CD/DVD images - Adds bounds checking (improves security) - No changes to filesystem mounting/unmounting logic - isofs is a mature, stable read-only filesystem **No API/ABI Changes**: All changes are internal to the isofs implementation. ### Conclusion This commit fixes user-visible bugs (garbage timestamps) in a mature filesystem subsystem with minimal risk. The Y2038/Y2156 fixes are exactly the type of important correctness issues that stable trees should address. The changes are well-contained, properly tested, and solve real problems that users will encounter.
fs/isofs/inode.c | 7 +++++-- fs/isofs/isofs.h | 4 +++- fs/isofs/rock.c | 40 ++++++++++++++++++++++----------------- fs/isofs/rock.h | 6 +----- fs/isofs/util.c | 49 +++++++++++++++++++++++++++++++----------------- 5 files changed, 64 insertions(+), 42 deletions(-)
diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c index 47038e6608123..d5da9817df9b3 100644 --- a/fs/isofs/inode.c +++ b/fs/isofs/inode.c @@ -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)); diff --git a/fs/isofs/isofs.h b/fs/isofs/isofs.h index 2d55207c9a990..5065558375333 100644 --- a/fs/isofs/isofs.h +++ b/fs/isofs/isofs.h @@ -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 */
diff --git a/fs/isofs/rock.c b/fs/isofs/rock.c index dbf911126e610..576498245b9d7 100644 --- a/fs/isofs/rock.c +++ b/fs/isofs/rock.c @@ -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; diff --git a/fs/isofs/rock.h b/fs/isofs/rock.h index 7755e587f7785..c0856fa9bb6a4 100644 --- a/fs/isofs/rock.h +++ b/fs/isofs/rock.h @@ -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 */ diff --git a/fs/isofs/util.c b/fs/isofs/util.c index e88dba7216618..42f479da0b282 100644 --- a/fs/isofs/util.c +++ b/fs/isofs/util.c @@ -16,29 +16,44 @@ * 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]; + ts.tv_nsec = 0; + /* High sierra has no time zone */ + tz = flags & ISO_DATE_HIGH_SIERRA ? 0 : p[6]; + }
- 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; - 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) tz |= (-1 << 8); - + /* * The timezone offset is unreliable on some disks, * so we make a sanity check. In no case is it ever @@ -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; +}