mirror of
https://github.com/torvalds/linux.git
synced 2026-10-07 02:56:02 +02:00
Total patches: 95 Reviews/patch: 0.63 Reviewed rate: 56% Summary of patch series in this merge: - "ocfs2/dlm: bound peer-controlled lengths in the o2dlm" (Bryam Vargas): Validate and bound all input lengths and count fields in the o2dlm migration and recovery receive handlers to prevent memory corruption and kernel panics from malformed cluster messages - "ocfs2: validate xattr entry bounds" (Cen Zhang): Validate OCFS2 extended attribute entry name and value bounds during metadata reads to prevent out-of-range memory accesses during retrieval or listing operations. - "taskstats: fix cgroupstats invalid fd handling and add selftests" (Yiyang Chen): Return -EBADF when cgroupstats receives an invalid file descriptor to prevent caller hangs and misleading success ACKs. Add a kselftest to validate valid cgroup v1 queries and verify proper error handling across different Netlink flag combinations. - "misc lib/raid/ improvements v2" (Christoph Hellwig): Improve benchmark-based algorithm selection for the XOR and RAID6 libraries, add KUnit benchmark tests, and cleanup minor implementation details. - "ocfs2: cluster: o2hb_region_pin() fixes" (Joseph Qi): Fix sleeping-in-atomic, lock order inversion and error-path cleanup bugs in o2hb_region_pin() by releasing o2hb_live_lock across sleeping configfs_depend_item() calls and using unlocked variants from callback context. Ensure failed pin attempts properly decrement user counts and unpin partially initialized heartbeat regions to prevent memory leaks and unprotected states. - "lib/ucs2_string.c: fix out-of-bounds read in ucs2_strnlen()" (Vincent Mailhol) Fix an off-by-one which could cause an out-of-bounds read. - "ocfs2: harden heartbeat teardown races" (Cen Zhang): Fix two OCFS2 heartbeat/o2net teardown races found by KASAN. - "taskstats: tidy up the cpumask command path" *Bradley Morgan) make two small cleanups in kernel/taskstats.c. - "ocfs2: validate active orphan slots during inode read" (ZhengYuan Huang): Validate active ordinary and append-DIO orphan slots read from OCFS2 dinodes at the metadata boundary to prevent corrupted slot indices from causing out-of-bounds array accesses. - "ocfs2: bound-check both readdir re-validation scans" (Zhan Xusheng) Enforce strict boundary checks on directory entry record lengths and offset calculations during OCFS2 directory re-scans to prevent out-of-bounds memory reads and directory position corruption. -----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQTTMBEPP41GrTpTJgfdBJ7gKXxAjgUCaoo38wAKCRDdBJ7gKXxA jnKuAP9tIUJoYIzxG7zr00qJj95gczgE0+GikN1wXfO9Cvh8QAEAqr5InWrANTBB l4qW3/o4hM9TlBdr84oT3RNnvBicuwA= =BY1d -----END PGP SIGNATURE----- Merge tag 'mm-nonmm-stable-2026-08-22-16-57' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm Pull non-MM updates from Andrew Morton: - "ocfs2/dlm: bound peer-controlled lengths in the o2dlm" (Bryam Vargas) Validate and bound all input lengths and count fields in the o2dlm migration and recovery receive handlers to prevent memory corruption and kernel panics from malformed cluster messages - "ocfs2: validate xattr entry bounds" (Cen Zhang) Validate OCFS2 extended attribute entry name and value bounds during metadata reads to prevent out-of-range memory accesses during retrieval or listing operations. - "taskstats: fix cgroupstats invalid fd handling and add selftests" (Yiyang Chen) Return -EBADF when cgroupstats receives an invalid file descriptor to prevent caller hangs and misleading success ACKs. Add a kselftest to validate valid cgroup v1 queries and verify proper error handling across different Netlink flag combinations. - "misc lib/raid/ improvements v2" (Christoph Hellwig) Improve benchmark-based algorithm selection for the XOR and RAID6 libraries, add KUnit benchmark tests, and cleanup minor implementation details. - "ocfs2: cluster: o2hb_region_pin() fixes" (Joseph Qi) Fix sleeping-in-atomic, lock order inversion and error-path cleanup bugs in o2hb_region_pin() by releasing o2hb_live_lock across sleeping configfs_depend_item() calls and using unlocked variants from callback context. Ensure failed pin attempts properly decrement user counts and unpin partially initialized heartbeat regions to prevent memory leaks and unprotected states. - "lib/ucs2_string.c: fix out-of-bounds read in ucs2_strnlen()" (Vincent Mailhol) Fix an off-by-one which could cause an out-of-bounds read. - "ocfs2: harden heartbeat teardown races" (Cen Zhang) Fix two OCFS2 heartbeat/o2net teardown races found by KASAN. - "taskstats: tidy up the cpumask command path" *Bradley Morgan) make two small cleanups in kernel/taskstats.c. - "ocfs2: validate active orphan slots during inode read" (ZhengYuan Huang) Validate active ordinary and append-DIO orphan slots read from OCFS2 dinodes at the metadata boundary to prevent corrupted slot indices from causing out-of-bounds array accesses. - "ocfs2: bound-check both readdir re-validation scans" (Zhan Xusheng) Enforce strict boundary checks on directory entry record lengths and offset calculations during OCFS2 directory re-scans to prevent out-of-bounds memory reads and directory position corruption. * tag 'mm-nonmm-stable-2026-08-22-16-57' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (95 commits) mailmap: fix bouncing address for Taniya Das ocfs2: bound-check dir entries in the inline-data re-validation scan ocfs2: bound-check dir entries in the readdir re-validation scan squashfs: avoid thundering-herd cache wakeups prctl: fix PR_SET_MM_AUXV losing the forced AT_NULL terminator mailmap: update email address for Linfeng Sun lib/interval_tree: fix allocation warning messages checkpatch: add NOKPROBE_SYMBOL to the whitelist of lines that can occur immediately after functions Squashfs: check block offset is not negative signal: factor out the kernel reserved si_code check ocfs2: fix readdir position truncation on 32-bit kernels ocfs2: fix cached cluster count after suballocator reclaim ocfs2: fix circular locking dependency in ocfs2_init_acl() ocfs2: validate DIO orphan slot during inode read ocfs2: validate orphan slot during inode read selftests/prctl: fix non-anonymous VMA mapping in set-anon-vma-name test MAINTAINERS: add IRC and patchwork for LTP include/linux/list.h: mark list_add and __list_add as __always_inline tools/mm: prevent page_owner_sort from truncating input hung_task: update DETECT_HUNG_TASK_BLOCKER Kconfig help ...
368 lines
9.9 KiB
C
368 lines
9.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* linux/fs/fat/misc.c
|
|
*
|
|
* Written 1992,1993 by Werner Almesberger
|
|
* 22/11/2000 - Fixed fat_date_unix2dos for dates earlier than 01/01/1980
|
|
* and date_dos2unix for date==0 by Igor Zhbanov(bsg@uniyar.ac.ru)
|
|
*/
|
|
|
|
#include "fat.h"
|
|
#include <linux/iversion.h>
|
|
|
|
/*
|
|
* fat_fs_error reports a file system problem that might indicate fa data
|
|
* corruption/inconsistency. Depending on 'errors' mount option the
|
|
* panic() is called, or error message is printed FAT and nothing is done,
|
|
* or filesystem is remounted read-only (default behavior).
|
|
* In case the file system is remounted read-only, it can be made writable
|
|
* again by remounting it.
|
|
*/
|
|
void __fat_fs_error(struct super_block *sb, int report, const char *fmt, ...)
|
|
{
|
|
struct fat_mount_options *opts = &MSDOS_SB(sb)->options;
|
|
va_list args;
|
|
struct va_format vaf;
|
|
|
|
if (report) {
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
fat_msg(sb, KERN_ERR, "error, %pV", &vaf);
|
|
va_end(args);
|
|
}
|
|
|
|
if (opts->errors == FAT_ERRORS_PANIC)
|
|
panic("FAT-fs (%s): fs panic from previous error\n", sb->s_id);
|
|
else if (opts->errors == FAT_ERRORS_RO && !sb_rdonly(sb)) {
|
|
sb->s_flags |= SB_RDONLY;
|
|
fat_msg(sb, KERN_ERR, "Filesystem has been set read-only");
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(__fat_fs_error);
|
|
|
|
/**
|
|
* _fat_msg() - Print a preformatted FAT message based on a superblock.
|
|
* @sb: A pointer to a &struct super_block
|
|
* @level: A Kernel printk level constant
|
|
* @fmt: The printf-style format string to print.
|
|
*
|
|
* Everything that is not fat_fs_error() should be fat_msg().
|
|
*
|
|
* fat_msg() wraps _fat_msg() for printk indexing.
|
|
*/
|
|
void _fat_msg(struct super_block *sb, const char *level, const char *fmt, ...)
|
|
{
|
|
struct va_format vaf;
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
_printk(FAT_PRINTK_PREFIX "%pV\n", level, sb->s_id, &vaf);
|
|
va_end(args);
|
|
}
|
|
|
|
/* Flushes the number of free clusters on FAT32 */
|
|
/* XXX: Need to write one per FSINFO block. Currently only writes 1 */
|
|
int fat_clusters_flush(struct super_block *sb)
|
|
{
|
|
struct msdos_sb_info *sbi = MSDOS_SB(sb);
|
|
struct buffer_head *bh;
|
|
struct fat_boot_fsinfo *fsinfo;
|
|
|
|
if (!is_fat32(sbi))
|
|
return 0;
|
|
|
|
bh = sb_bread(sb, sbi->fsinfo_sector);
|
|
if (bh == NULL) {
|
|
fat_msg(sb, KERN_ERR, "bread failed in fat_clusters_flush");
|
|
return -EIO;
|
|
}
|
|
|
|
fsinfo = (struct fat_boot_fsinfo *)bh->b_data;
|
|
/* Sanity check */
|
|
if (!IS_FSINFO(fsinfo)) {
|
|
fat_msg(sb, KERN_ERR, "Invalid FSINFO signature: "
|
|
"0x%08x, 0x%08x (sector = %lu)",
|
|
le32_to_cpu(fsinfo->signature1),
|
|
le32_to_cpu(fsinfo->signature2),
|
|
sbi->fsinfo_sector);
|
|
} else {
|
|
if (sbi->free_clusters != -1)
|
|
fsinfo->free_clusters = cpu_to_le32(sbi->free_clusters);
|
|
if (sbi->prev_free != -1)
|
|
fsinfo->next_cluster = cpu_to_le32(sbi->prev_free);
|
|
mark_buffer_dirty(bh);
|
|
}
|
|
brelse(bh);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* fat_chain_add() adds a new cluster to the chain of clusters represented
|
|
* by inode.
|
|
*/
|
|
int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)
|
|
{
|
|
struct super_block *sb = inode->i_sb;
|
|
struct msdos_sb_info *sbi = MSDOS_SB(sb);
|
|
int ret, new_fclus, last;
|
|
|
|
/*
|
|
* We must locate the last cluster of the file to add this new
|
|
* one (new_dclus) to the end of the link list (the FAT).
|
|
*/
|
|
last = new_fclus = 0;
|
|
if (MSDOS_I(inode)->i_start) {
|
|
int fclus, dclus;
|
|
|
|
ret = fat_get_cluster(inode, FAT_ENT_EOF, &fclus, &dclus);
|
|
if (ret < 0)
|
|
return ret;
|
|
new_fclus = fclus + 1;
|
|
last = dclus;
|
|
}
|
|
|
|
/* add new one to the last of the cluster chain */
|
|
if (last) {
|
|
struct fat_entry fatent;
|
|
|
|
fatent_init(&fatent);
|
|
ret = fat_ent_read(inode, &fatent, last);
|
|
if (ret >= 0) {
|
|
int wait = inode_needs_sync(inode);
|
|
int old = ret;
|
|
|
|
ret = fat_ent_write(inode, &fatent, new_dclus, wait);
|
|
if (ret < 0)
|
|
fat_ent_write(inode, &fatent, old, wait);
|
|
fatent_brelse(&fatent);
|
|
}
|
|
if (ret < 0)
|
|
return ret;
|
|
/*
|
|
* FIXME:Although we can add this cache, fat_cache_add() is
|
|
* assuming to be called after linear search with fat_cache_id.
|
|
*/
|
|
// fat_cache_add(inode, new_fclus, new_dclus);
|
|
} else {
|
|
MSDOS_I(inode)->i_start = new_dclus;
|
|
MSDOS_I(inode)->i_logstart = new_dclus;
|
|
mark_inode_dirty(inode);
|
|
/*
|
|
* Since generic_write_sync() synchronizes regular files later,
|
|
* we sync here only directories.
|
|
*/
|
|
if (S_ISDIR(inode->i_mode) && IS_DIRSYNC(inode)) {
|
|
ret = sync_inode_metadata(inode, 1);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
}
|
|
if (new_fclus != (inode->i_blocks >> (sbi->cluster_bits - 9))) {
|
|
fat_fs_error_ratelimit(
|
|
sb, "clusters badly computed (%d != %llu)", new_fclus,
|
|
(llu)(inode->i_blocks >> (sbi->cluster_bits - 9)));
|
|
fat_cache_inval_inode(inode);
|
|
}
|
|
inode->i_blocks += nr_cluster << (sbi->cluster_bits - 9);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The epoch of FAT timestamp is 1980.
|
|
* : bits : value
|
|
* date: 0 - 4: day (1 - 31)
|
|
* date: 5 - 8: month (1 - 12)
|
|
* date: 9 - 15: year (0 - 127) from 1980
|
|
* time: 0 - 4: sec (0 - 29) 2sec counts
|
|
* time: 5 - 10: min (0 - 59)
|
|
* time: 11 - 15: hour (0 - 23)
|
|
*/
|
|
#define SECS_PER_MIN 60
|
|
#define SECS_PER_HOUR (60 * 60)
|
|
#define SECS_PER_DAY (SECS_PER_HOUR * 24)
|
|
/* days between 1.1.70 and 1.1.80 (2 leap days) */
|
|
#define DAYS_DELTA (365 * 10 + 2)
|
|
/* 120 (2100 - 1980) isn't leap year */
|
|
#define YEAR_2100 120
|
|
#define IS_LEAP_YEAR(y) (!((y) & 3) && (y) != YEAR_2100)
|
|
|
|
/* Linear day numbers of the respective 1sts in non-leap years. */
|
|
static long days_in_year[] = {
|
|
/* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
|
|
0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0,
|
|
};
|
|
|
|
static inline int fat_tz_offset(const struct msdos_sb_info *sbi)
|
|
{
|
|
return (sbi->options.tz_set ?
|
|
-sbi->options.time_offset :
|
|
sys_tz.tz_minuteswest) * SECS_PER_MIN;
|
|
}
|
|
|
|
/* Convert a FAT time/date pair to a UNIX date (seconds since 1 1 70). */
|
|
void fat_time_fat2unix(struct msdos_sb_info *sbi, struct timespec64 *ts,
|
|
__le16 __time, __le16 __date, u8 time_cs)
|
|
{
|
|
u16 time = le16_to_cpu(__time), date = le16_to_cpu(__date);
|
|
time64_t second;
|
|
long day, leap_day, month, year;
|
|
|
|
year = date >> 9;
|
|
month = max(1, (date >> 5) & 0xf);
|
|
day = max(1, date & 0x1f) - 1;
|
|
|
|
leap_day = (year + 3) / 4;
|
|
if (year > YEAR_2100) /* 2100 isn't leap year */
|
|
leap_day--;
|
|
if (IS_LEAP_YEAR(year) && month > 2)
|
|
leap_day++;
|
|
|
|
second = (time & 0x1f) << 1;
|
|
second += ((time >> 5) & 0x3f) * SECS_PER_MIN;
|
|
second += (time >> 11) * SECS_PER_HOUR;
|
|
second += (time64_t)(year * 365 + leap_day
|
|
+ days_in_year[month] + day
|
|
+ DAYS_DELTA) * SECS_PER_DAY;
|
|
|
|
second += fat_tz_offset(sbi);
|
|
|
|
if (time_cs) {
|
|
ts->tv_sec = second + (time_cs / 100);
|
|
ts->tv_nsec = (time_cs % 100) * 10000000;
|
|
} else {
|
|
ts->tv_sec = second;
|
|
ts->tv_nsec = 0;
|
|
}
|
|
}
|
|
|
|
/* Export fat_time_fat2unix() for the fat_test KUnit tests. */
|
|
EXPORT_SYMBOL_GPL(fat_time_fat2unix);
|
|
|
|
/* Convert linear UNIX date to a FAT time/date pair. */
|
|
void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec64 *ts,
|
|
__le16 *time, __le16 *date, u8 *time_cs)
|
|
{
|
|
struct tm tm;
|
|
time64_to_tm(ts->tv_sec, -fat_tz_offset(sbi), &tm);
|
|
|
|
/* FAT can only support year between 1980 to 2107 */
|
|
if (tm.tm_year < 1980 - 1900) {
|
|
*time = 0;
|
|
*date = cpu_to_le16((0 << 9) | (1 << 5) | 1);
|
|
if (time_cs)
|
|
*time_cs = 0;
|
|
return;
|
|
}
|
|
if (tm.tm_year > 2107 - 1900) {
|
|
*time = cpu_to_le16((23 << 11) | (59 << 5) | 29);
|
|
*date = cpu_to_le16((127 << 9) | (12 << 5) | 31);
|
|
if (time_cs)
|
|
*time_cs = 199;
|
|
return;
|
|
}
|
|
|
|
/* from 1900 -> from 1980 */
|
|
tm.tm_year -= 80;
|
|
/* 0~11 -> 1~12 */
|
|
tm.tm_mon++;
|
|
/* 0~59 -> 0~29(2sec counts) */
|
|
tm.tm_sec >>= 1;
|
|
|
|
*time = cpu_to_le16(tm.tm_hour << 11 | tm.tm_min << 5 | tm.tm_sec);
|
|
*date = cpu_to_le16(tm.tm_year << 9 | tm.tm_mon << 5 | tm.tm_mday);
|
|
if (time_cs)
|
|
*time_cs = (ts->tv_sec & 1) * 100 + ts->tv_nsec / 10000000;
|
|
}
|
|
EXPORT_SYMBOL_GPL(fat_time_unix2fat);
|
|
|
|
static inline struct timespec64 fat_timespec64_trunc_2secs(struct timespec64 ts)
|
|
{
|
|
return (struct timespec64){ ts.tv_sec & ~1ULL, 0 };
|
|
}
|
|
|
|
/*
|
|
* truncate atime to 24 hour granularity (00:00:00 in local timezone)
|
|
*/
|
|
struct timespec64 fat_truncate_atime(const struct msdos_sb_info *sbi,
|
|
const struct timespec64 *ts)
|
|
{
|
|
/* to localtime */
|
|
time64_t seconds = ts->tv_sec - fat_tz_offset(sbi);
|
|
s32 remainder;
|
|
|
|
div_s64_rem(seconds, SECS_PER_DAY, &remainder);
|
|
/* to day boundary, and back to unix time */
|
|
seconds = seconds + fat_tz_offset(sbi) - remainder;
|
|
|
|
return (struct timespec64){ seconds, 0 };
|
|
}
|
|
/* Export fat_truncate_atime() for the fat_test KUnit tests. */
|
|
EXPORT_SYMBOL_GPL(fat_truncate_atime);
|
|
|
|
/*
|
|
* Update the in-inode atime and/or mtime after truncating the timestamp to the
|
|
* granularity. All timestamps in root inode are always 0.
|
|
*
|
|
* ctime and mtime share the same on-disk field, and should be identical in
|
|
* memory. All mtime updates will be applied to ctime, but ctime updates are
|
|
* ignored.
|
|
*/
|
|
void fat_truncate_time(struct inode *inode, struct timespec64 *now,
|
|
unsigned int flags)
|
|
{
|
|
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
|
|
struct timespec64 ts;
|
|
|
|
if (inode->i_ino == MSDOS_ROOT_INO)
|
|
return;
|
|
|
|
if (now == NULL) {
|
|
now = &ts;
|
|
ts = current_time(inode);
|
|
}
|
|
|
|
if (flags & FAT_UPDATE_ATIME)
|
|
inode_set_atime_to_ts(inode, fat_truncate_atime(sbi, now));
|
|
if (flags & FAT_UPDATE_CMTIME) {
|
|
/* truncate mtime to 2 second granularity */
|
|
struct timespec64 mtime = fat_timespec64_trunc_2secs(*now);
|
|
|
|
inode_set_mtime_to_ts(inode, mtime);
|
|
inode_set_ctime_to_ts(inode, mtime);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(fat_truncate_time);
|
|
|
|
int fat_update_time(struct inode *inode, enum fs_update_time type,
|
|
unsigned int flags)
|
|
{
|
|
if (inode->i_ino != MSDOS_ROOT_INO) {
|
|
fat_truncate_time(inode, NULL, type == FS_UPD_ATIME ?
|
|
FAT_UPDATE_ATIME : FAT_UPDATE_CMTIME);
|
|
__mark_inode_dirty(inode, inode_time_dirty_flag(inode));
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(fat_update_time);
|
|
|
|
int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs)
|
|
{
|
|
int i, err = 0;
|
|
|
|
for (i = 0; i < nr_bhs; i++)
|
|
write_dirty_buffer(bhs[i], 0);
|
|
|
|
for (i = 0; i < nr_bhs; i++) {
|
|
wait_on_buffer(bhs[i]);
|
|
if (!err && !buffer_uptodate(bhs[i]))
|
|
err = -EIO;
|
|
}
|
|
return err;
|
|
}
|