Root Cause Analysis
1. Functions, Structs, Fields, and Files Involved
- File:
fs/f2fs/super.c
- Functions:
f2fs_apply_quota_options()
__f2fs_remount()
f2fs_unnote_qf_name()
f2fs_fill_super()
- Structs & Fields:
struct f2fs_sb_info: sbi->mount_opt.s_qf_names[MAXQUOTAS] (F2FS_OPTION(sbi).s_qf_names)
struct f2fs_mount_info: org_mount_opt.s_qf_names[MAXQUOTAS]
struct f2fs_fs_context: ctx->qname_mask, ctx->info.s_qf_names[MAXQUOTAS] (F2FS_CTX_INFO(ctx).s_qf_names)
2. Trigger Mechanism and Lifecycle Invariant Violation
During initial mount with journalled quota specified (e.g., usrjquota=f2fs), the option parser notes the quota filename, and f2fs_apply_quota_options() (called via f2fs_fill_super() -> f2fs_apply_options()) allocates an active copy of the filename string via kstrdup():
/* fs/f2fs/super.c:1660-1665 */
qname = F2FS_CTX_INFO(ctx).s_qf_names[i];
if (qname) {
qname = kstrdup(F2FS_CTX_INFO(ctx).s_qf_names[i], GFP_KERNEL | __GFP_NOFAIL);
set_opt(sbi, QUOTA);
}
F2FS_OPTION(sbi).s_qf_names[i] = qname;
This string pointer (F2FS_OPTION(sbi).s_qf_names[i]) is held by the superblock info and expected to be freed either on unmount (f2fs_put_super()) or when the option is updated/cleared.
When userspace subsequently issues a remount clearing the quota filename (e.g., mount(..., MS_REMOUNT, "usrjquota="), as seen in the reproducer):
- In
f2fs_parse_param(), f2fs_unnote_qf_name() is invoked, which sets ctx->info.s_qf_names[USRQUOTA] = NULL and records the change in ctx->qname_mask |= (1 << USRQUOTA).
- In
__f2fs_remount(), backup options are prepared in org_mount_opt. Crucially, __f2fs_remount() allocates a new duplicate of the existing filename into org_mount_opt.s_qf_names[i] via kstrdup():
/* fs/f2fs/super.c:2872-2876 */
for (i = 0; i < MAXQUOTAS; i++) {
if (F2FS_OPTION(sbi).s_qf_names[i]) {
org_mount_opt.s_qf_names[i] =
kstrdup(F2FS_OPTION(sbi).s_qf_names[i], GFP_KERNEL);
...
- Then
f2fs_apply_options() calls f2fs_apply_quota_options():
/* fs/f2fs/super.c:1655-1666 */
for (i = 0; i < MAXQUOTAS; i++) {
if (!(ctx->qname_mask & (1 << i)))
continue;
qname = F2FS_CTX_INFO(ctx).s_qf_names[i];
if (qname) {
qname = kstrdup(F2FS_CTX_INFO(ctx).s_qf_names[i],
GFP_KERNEL | __GFP_NOFAIL);
set_opt(sbi, QUOTA);
}
F2FS_OPTION(sbi).s_qf_names[i] = qname;
}
Because qname is NULL, F2FS_OPTION(sbi).s_qf_names[i] is overwritten with NULL without freeing the old pointer in F2FS_OPTION(sbi).s_qf_names[i].
- When
__f2fs_remount() finishes successfully:
/* fs/f2fs/super.c:3100-3101 */
for (i = 0; i < MAXQUOTAS; i++)
kfree(org_mount_opt.s_qf_names[i]);
__f2fs_remount() frees org_mount_opt.s_qf_names[i], which is the backup copy allocated at step 2.
- The original string allocated during initial mount at
f2fs_apply_quota_options() is now completely unreferenced by any kernel pointer and is permanently leaked.
3. Runtime Consequences in a Production Kernel
- Memory Corruption (
false): The defect is an omitted kfree(). Pointers are dropped without being freed; there is no use-after-free, double free, wild pointer dereference, or out-of-bounds access.
- Information Leak (
none): The orphaned heap buffer is not exposed or copied to userspace.
- Raw Denial of Service (
true): Repeatedly mounting and remounting filesystems with cleared quota options permanently leaks slab memory in the kernel (size-8 or larger kmalloc cache), constituting a kernel resource/memory leak DoS.
- Networking / KVM Candidate (
false): The issue is entirely confined to the F2FS filesystem mount option reconfiguration logic.