* [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits
@ 2026-08-14 11:46 Kitae Yoo
2026-08-14 11:46 ` [RFC PATCH 1/2] quota: allow DQF_ROOT_SQUASH on all quota formats Kitae Yoo
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Kitae Yoo @ 2026-08-14 11:46 UTC (permalink / raw)
To: Jan Kara; +Cc: linux-fsdevel, linux-ext4, linux-kernel, Kitae Yoo
ignore_hardlimit() exempts CAP_SYS_RESOURCE holders from enforcement of
hard limits (and of soft limits past their grace time), regardless of
quota type, so it also covers project quotas.
For user/group quotas this matches the long-standing expectation that an
administrator can act on a full filesystem. For project quotas it is a
poor fit: a project limit bounds the size of a directory tree rather than
restricting a user, and is commonly used for capacity isolation of
container volumes and NFS-exported shares. There the exemption defeats
the purpose. Two concrete cases:
1. A process holding CAP_SYS_RESOURCE (e.g. a privileged container)
writes past a project hard limit; accounting keeps rising above it.
2. knfsd raises CAP_SYS_RESOURCE for requests mapped to root on
no_root_squash exports (CAP_NFSD_SET in <linux/capability.h>). So on
an ext4-backed, no_root_squash NFS export any remote root write
bypasses project hard limits, no matter how confined the client is.
We hit this while evaluating ext4 project quotas for multi-tenant NFS
volumes: with a 2 GiB hard limit, remote root writes proceeded well
past 2 GiB (v6.8, quotaon reporting "enforced"). The same setup on
XFS, whose enforcement path performs no capability check, stops the
write at the limit.
DQF_ROOT_SQUASH already disables this exemption, but setting it has been
confined to the old v1 quota format, and commit ca6cb0918e87 ("quota:
Verify flags passed to Q_SETINFO") later made Q_SETINFO reject it
explicitly on other formats, on the grounds that those formats did not
persist the flag and a flag silently lost on remount is confusing. That
confinement predates generic project quota support (commit 847aac644e92
"vfs: Add general support to enforce project quota limits").
This series lifts the restriction and addresses the persistence concern
that motivated it:
1/2 allows DQF_ROOT_SQUASH to be set through Q_SETINFO on all formats,
so it can be enabled per quota type (e.g. project only).
2/2 persists the flag in the v2 on-disk dqi_flags field, which already
exists, masking on read so only the supported flag is honoured -
which also keeps the pre-existing unvalidated on-disk bits (the
reason c119c5b9749e "quota: Don't store flags for v2 quota format"
stopped storing them) out of the in-memory state.
No behaviour change by default: the flag stays clear unless explicitly
set, and setting it on a non-v1 format previously returned -EINVAL.
Accepting it there is the user-visible ABI change 1/2 makes.
Open questions for reviewers:
* Is extending DQF_ROOT_SQUASH the right vehicle? Despite its name it
has always controlled the CAP_SYS_RESOURCE exemption in the generic
dquot path, not UID squashing, so the semantic already matches. The
alternative is a newly named per-type flag, at the cost of new uAPI.
I lean toward reusing the existing flag but defer to your preference;
the uAPI comment is updated to describe the real semantic either way.
* Is persisting the flag in the v2 dqi_flags field (2/2) acceptable? An
older kernel rewriting quota info still clears it, so strict
enforcement would need to be set up again after booting such a kernel.
* The NFS case is really a knfsd credential question (should
no_root_squash grant CAP_SYS_RESOURCE?). Fixing it in quota covers the
privileged-container case too and needs no per-export policy, which is
why I bring it here rather than to the nfsd maintainers, but I'm happy
to pursue that side there if preferred.
Reproducer (ext4, as root on a scratch device $DEV):
mkfs.ext4 -qF -O project,quota -E quotatype=prjquota $DEV
mount -o prjquota $DEV /mnt
mkdir /mnt/vol && chattr +P -p 42 /mnt/vol
setquota -P 42 0 $((100*1024)) 0 0 /mnt # 100 MiB project hard limit
# root write, flag clear: exceeds the limit (the bug)
dd if=/dev/zero of=/mnt/vol/f bs=1M count=200 oflag=direct
du -m /mnt/vol/f # ~200
./set_rsquash /mnt # Q_SETINFO(PRJQUOTA, DQF_ROOT_SQUASH); no CLI for this
rm /mnt/vol/f
dd if=/dev/zero of=/mnt/vol/f bs=1M count=200 oflag=direct
du -m /mnt/vol/f # ~100, now enforced
# 2/2: the flag survives a remount
umount /mnt && mount -o prjquota $DEV /mnt
dd if=/dev/zero of=/mnt/vol/g bs=1M count=200 oflag=direct
du -m /mnt/vol/g # ~100
On XFS the first write already stops at 100 MiB. set_rsquash.c:
#include <sys/quota.h>
#include <linux/quota.h>
int main(int argc, char **argv)
{
struct if_dqinfo info;
if (quotactl(QCMD(Q_GETINFO, PRJQUOTA), argv[1], 0, (void *)&info))
return 1;
info.dqi_flags |= DQF_ROOT_SQUASH;
info.dqi_valid = IIF_FLAGS;
return quotactl(QCMD(Q_SETINFO, PRJQUOTA), argv[1], 0, (void *)&info)
? 1 : 0;
}
I can turn the enforcement check into an fstest.
Kitae Yoo (2):
quota: allow DQF_ROOT_SQUASH on all quota formats
quota_v2: persist DQF_ROOT_SQUASH
fs/quota/dquot.c | 8 +-------
fs/quota/quota_v2.c | 6 ++----
include/uapi/linux/quota.h | 2 +-
3 files changed, 4 insertions(+), 12 deletions(-)
--
2.50.1 (Apple Git-155)
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH 1/2] quota: allow DQF_ROOT_SQUASH on all quota formats
2026-08-14 11:46 [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits Kitae Yoo
@ 2026-08-14 11:46 ` Kitae Yoo
2026-08-14 12:01 ` sashiko-bot
2026-08-14 11:46 ` [RFC PATCH 2/2] quota_v2: persist DQF_ROOT_SQUASH Kitae Yoo
2026-08-25 15:28 ` [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits Jan Kara
2 siblings, 1 reply; 6+ messages in thread
From: Kitae Yoo @ 2026-08-14 11:46 UTC (permalink / raw)
To: Jan Kara; +Cc: linux-fsdevel, linux-ext4, linux-kernel, Kitae Yoo
ignore_hardlimit() exempts CAP_SYS_RESOURCE holders from enforcement of
hard limits and of soft limits whose grace time expired.
DQF_ROOT_SQUASH disables that exemption, but it has been confined to the
old v1 quota format since the quota format abstraction was introduced,
and commit ca6cb0918e87 ("quota: Verify flags passed to Q_SETINFO")
later made Q_SETINFO reject it explicitly on other formats.
That confinement predates generic project quota support. Project limits
bound the size of a directory tree rather than restrict a user, and are
commonly used for capacity isolation of container volumes and NFS
exports. There the exemption defeats the purpose: knfsd raises
CAP_SYS_RESOURCE for requests mapped to root on no_root_squash exports
(CAP_NFSD_SET), so any remote root write silently exceeds project hard
limits. XFS enforces project limits regardless of capabilities.
Lift the format restriction so the flag can be set per quota type
through Q_SETINFO on journaled quota as well. Existing setups keep their
behaviour: the flag stays clear unless explicitly set, and setting it
was previously rejected with -EINVAL on anything but QFMT_VFS_OLD -
accepting it there is the user-visible ABI change this patch makes.
Signed-off-by: Kitae Yoo <kitaeyoo777@gmail.com>
---
fs/quota/dquot.c | 8 +-------
include/uapi/linux/quota.h | 2 +-
2 files changed, 2 insertions(+), 8 deletions(-)
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index 9850de3955..e431e72dfe 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -1309,8 +1309,7 @@ static int ignore_hardlimit(struct dquot *dquot)
struct mem_dqinfo *info = &sb_dqopt(dquot->dq_sb)->info[dquot->dq_id.type];
return capable(CAP_SYS_RESOURCE) &&
- (info->dqi_format->qf_fmt_id != QFMT_VFS_OLD ||
- !(info->dqi_flags & DQF_ROOT_SQUASH));
+ !(info->dqi_flags & DQF_ROOT_SQUASH);
}
static int dquot_add_inodes(struct dquot *dquot, qsize_t inodes,
@@ -2900,11 +2899,6 @@ int dquot_set_dqinfo(struct super_block *sb, int type, struct qc_info *ii)
if (!sb_has_quota_active(sb, type))
return -ESRCH;
mi = sb_dqopt(sb)->info + type;
- if (ii->i_fieldmask & QC_FLAGS) {
- if ((ii->i_flags & QCI_ROOT_SQUASH &&
- mi->dqi_format->qf_fmt_id != QFMT_VFS_OLD))
- return -EINVAL;
- }
spin_lock(&dq_data_lock);
if (ii->i_fieldmask & QC_SPC_TIMER)
mi->dqi_bgrace = ii->i_spc_timelimit;
diff --git a/include/uapi/linux/quota.h b/include/uapi/linux/quota.h
index 52090105b8..a34f43519a 100644
--- a/include/uapi/linux/quota.h
+++ b/include/uapi/linux/quota.h
@@ -149,7 +149,7 @@ enum {
DQF_PRIVATE
};
-/* Root squash enabled (for v1 quota format) */
+/* Enforce limits also for CAP_SYS_RESOURCE processes */
#define DQF_ROOT_SQUASH (1 << DQF_ROOT_SQUASH_B)
/* Quota stored in a system file */
#define DQF_SYS_FILE (1 << DQF_SYS_FILE_B)
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [RFC PATCH 2/2] quota_v2: persist DQF_ROOT_SQUASH
2026-08-14 11:46 [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits Kitae Yoo
2026-08-14 11:46 ` [RFC PATCH 1/2] quota: allow DQF_ROOT_SQUASH on all quota formats Kitae Yoo
@ 2026-08-14 11:46 ` Kitae Yoo
2026-08-14 11:56 ` sashiko-bot
2026-08-25 15:28 ` [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits Jan Kara
2 siblings, 1 reply; 6+ messages in thread
From: Kitae Yoo @ 2026-08-14 11:46 UTC (permalink / raw)
To: Jan Kara; +Cc: linux-fsdevel, linux-ext4, linux-kernel, Kitae Yoo
Commit c119c5b9749e ("quota: Don't store flags for v2 quota format")
stopped persisting dqi_flags because no v2 flag was supported and
on-disk flags could contain unvalidated garbage. Now that
DQF_ROOT_SQUASH is settable on v2, losing it across quotaoff/quotaon or
remount would silently re-enable the CAP_SYS_RESOURCE exemption - a poor
property for an enforcement policy.
Store the flag in the existing on-disk dqi_flags field and mask on read
so only the supported flag is ever accepted from disk, which also keeps
pre-existing garbage bits out of the in-memory flags.
An older kernel rewriting quota info still clears the stored flag;
strict enforcement then needs to be set up again after booting back.
Signed-off-by: Kitae Yoo <kitaeyoo777@gmail.com>
---
fs/quota/quota_v2.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c
index a24fab5f9f..814db61681 100644
--- a/fs/quota/quota_v2.c
+++ b/fs/quota/quota_v2.c
@@ -142,8 +142,7 @@ static int v2_read_file_info(struct super_block *sb, int type)
}
info->dqi_bgrace = le32_to_cpu(dinfo.dqi_bgrace);
info->dqi_igrace = le32_to_cpu(dinfo.dqi_igrace);
- /* No flags currently supported */
- info->dqi_flags = 0;
+ info->dqi_flags = le32_to_cpu(dinfo.dqi_flags) & DQF_ROOT_SQUASH;
qinfo->dqi_sb = sb;
qinfo->dqi_type = type;
qinfo->dqi_blocks = le32_to_cpu(dinfo.dqi_blocks);
@@ -209,8 +208,7 @@ static int v2_write_file_info(struct super_block *sb, int type)
info->dqi_flags &= ~DQF_INFO_DIRTY;
dinfo.dqi_bgrace = cpu_to_le32(info->dqi_bgrace);
dinfo.dqi_igrace = cpu_to_le32(info->dqi_igrace);
- /* No flags currently supported */
- dinfo.dqi_flags = cpu_to_le32(0);
+ dinfo.dqi_flags = cpu_to_le32(info->dqi_flags & DQF_ROOT_SQUASH);
spin_unlock(&dq_data_lock);
dinfo.dqi_blocks = cpu_to_le32(qinfo->dqi_blocks);
dinfo.dqi_free_blk = cpu_to_le32(qinfo->dqi_free_blk);
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [RFC PATCH 2/2] quota_v2: persist DQF_ROOT_SQUASH
2026-08-14 11:46 ` [RFC PATCH 2/2] quota_v2: persist DQF_ROOT_SQUASH Kitae Yoo
@ 2026-08-14 11:56 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-08-14 11:56 UTC (permalink / raw)
To: Kitae Yoo; +Cc: tytso, linux-ext4
> Commit c119c5b9749e ("quota: Don't store flags for v2 quota format")
> stopped persisting dqi_flags because no v2 flag was supported and
> on-disk flags could contain unvalidated garbage. Now that
> DQF_ROOT_SQUASH is settable on v2, losing it across quotaoff/quotaon or
> remount would silently re-enable the CAP_SYS_RESOURCE exemption - a poor
> property for an enforcement policy.
>
> Store the flag in the existing on-disk dqi_flags field and mask on read
> so only the supported flag is ever accepted from disk, which also keeps
> pre-existing garbage bits out of the in-memory flags.
>
> An older kernel rewriting quota info still clears the stored flag;
> strict enforcement then needs to be set up again after booting back.
>
> Signed-off-by: Kitae Yoo <kitaeyoo777@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260814114649.51253-1-kitaeyoo777@gmail.com?part=2
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [RFC PATCH 1/2] quota: allow DQF_ROOT_SQUASH on all quota formats
2026-08-14 11:46 ` [RFC PATCH 1/2] quota: allow DQF_ROOT_SQUASH on all quota formats Kitae Yoo
@ 2026-08-14 12:01 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-08-14 12:01 UTC (permalink / raw)
To: Kitae Yoo; +Cc: linux-ext4, tytso
> ignore_hardlimit() exempts CAP_SYS_RESOURCE holders from enforcement of
> hard limits and of soft limits whose grace time expired.
> DQF_ROOT_SQUASH disables that exemption, but it has been confined to the
> old v1 quota format since the quota format abstraction was introduced,
> and commit ca6cb0918e87 ("quota: Verify flags passed to Q_SETINFO")
> later made Q_SETINFO reject it explicitly on other formats.
>
> That confinement predates generic project quota support. Project limits
> bound the size of a directory tree rather than restrict a user, and are
> commonly used for capacity isolation of container volumes and NFS
> exports. There the exemption defeats the purpose: knfsd raises
> CAP_SYS_RESOURCE for requests mapped to root on no_root_squash exports
> (CAP_NFSD_SET), so any remote root write silently exceeds project hard
> limits. XFS enforces project limits regardless of capabilities.
>
> Lift the format restriction so the flag can be set per quota type
> through Q_SETINFO on journaled quota as well. Existing setups keep their
> behaviour: the flag stays clear unless explicitly set, and setting it
> was previously rejected with -EINVAL on anything but QFMT_VFS_OLD -
> accepting it there is the user-visible ABI change this patch makes.
>
> Signed-off-by: Kitae Yoo <kitaeyoo777@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260814114649.51253-1-kitaeyoo777@gmail.com?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits
2026-08-14 11:46 [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits Kitae Yoo
2026-08-14 11:46 ` [RFC PATCH 1/2] quota: allow DQF_ROOT_SQUASH on all quota formats Kitae Yoo
2026-08-14 11:46 ` [RFC PATCH 2/2] quota_v2: persist DQF_ROOT_SQUASH Kitae Yoo
@ 2026-08-25 15:28 ` Jan Kara
2 siblings, 0 replies; 6+ messages in thread
From: Jan Kara @ 2026-08-25 15:28 UTC (permalink / raw)
To: Kitae Yoo; +Cc: Jan Kara, linux-fsdevel, linux-ext4, linux-kernel
On Fri 14-08-26 20:46:47, Kitae Yoo wrote:
> ignore_hardlimit() exempts CAP_SYS_RESOURCE holders from enforcement of
> hard limits (and of soft limits past their grace time), regardless of
> quota type, so it also covers project quotas.
>
> For user/group quotas this matches the long-standing expectation that an
> administrator can act on a full filesystem. For project quotas it is a
> poor fit: a project limit bounds the size of a directory tree rather than
> restricting a user, and is commonly used for capacity isolation of
> container volumes and NFS-exported shares. There the exemption defeats
> the purpose. Two concrete cases:
Project quotas *can* be used for restricting size of the directory tree.
But it is not the only way how to use them (nothing really forces each
project files to form a tree).
> 1. A process holding CAP_SYS_RESOURCE (e.g. a privileged container)
> writes past a project hard limit; accounting keeps rising above it.
>
> 2. knfsd raises CAP_SYS_RESOURCE for requests mapped to root on
> no_root_squash exports (CAP_NFSD_SET in <linux/capability.h>). So on
> an ext4-backed, no_root_squash NFS export any remote root write
> bypasses project hard limits, no matter how confined the client is.
> We hit this while evaluating ext4 project quotas for multi-tenant NFS
> volumes: with a 2 GiB hard limit, remote root writes proceeded well
> past 2 GiB (v6.8, quotaon reporting "enforced"). The same setup on
> XFS, whose enforcement path performs no capability check, stops the
> write at the limit.
I would consider this a desirable behavior, not a bug. If you have
priviledged user, he/she should be able to overcome artificially imposed
limits. That was the design of quota subsystem from the early days. Running
as priviledged user in a container or without rootsquash on NFS is IMO a
bad security practice and such user can generally overcome the limits (e.g.
by changing file owners including owner project). So not only would this
break a long standing practice, I also don't see how this would
significantly change anything.
Sadly, XFS always had a separate quota implementation and as a result there
are subtle behavioral differences. In particular limits for
user/group/project 0 are treated as default limits for any user with unset
limits. I don't think they are expected to constrain root user.
> DQF_ROOT_SQUASH already disables this exemption, but setting it has been
> confined to the old v1 quota format, and commit ca6cb0918e87 ("quota:
> Verify flags passed to Q_SETINFO") later made Q_SETINFO reject it
> explicitly on other formats, on the grounds that those formats did not
> persist the flag and a flag silently lost on remount is confusing. That
> confinement predates generic project quota support (commit 847aac644e92
> "vfs: Add general support to enforce project quota limits").
DQF_ROOT_SQUASH was always intended to be limited to the old quota format.
For newer formats, we always allow setting limits even for
user/group/project 0 but we also respect process capabilities when checking
whether it can overcome the limits or not.
> This series lifts the restriction and addresses the persistence concern
> that motivated it:
>
> 1/2 allows DQF_ROOT_SQUASH to be set through Q_SETINFO on all formats,
> so it can be enabled per quota type (e.g. project only).
> 2/2 persists the flag in the v2 on-disk dqi_flags field, which already
> exists, masking on read so only the supported flag is honoured -
> which also keeps the pre-existing unvalidated on-disk bits (the
> reason c119c5b9749e "quota: Don't store flags for v2 quota format"
> stopped storing them) out of the in-memory state.
Thank you for your proposal but essentially you create a flag saying
"ignore CAP_SYS_RESOURCE for project quotas". As much as I can see how it
would be convenient for your setup, I think that is a bad design and would
create confusion down the road. All I can really recommend is to properly
separate priviledges for your processes and then you wouldn't have to come
up with hacks to ignore CAP_SYS_RESOURCE...
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-26 7:52 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-14 11:46 [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits Kitae Yoo
2026-08-14 11:46 ` [RFC PATCH 1/2] quota: allow DQF_ROOT_SQUASH on all quota formats Kitae Yoo
2026-08-14 12:01 ` sashiko-bot
2026-08-14 11:46 ` [RFC PATCH 2/2] quota_v2: persist DQF_ROOT_SQUASH Kitae Yoo
2026-08-14 11:56 ` sashiko-bot
2026-08-25 15:28 ` [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits Jan Kara
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox