Linux EXT4 FS development
 help / color / mirror / Atom feed
From: Kitae Yoo <kitaeyoo777@gmail.com>
To: Jan Kara <jack@suse.com>
Cc: linux-fsdevel@vger.kernel.org, linux-ext4@vger.kernel.org,
	linux-kernel@vger.kernel.org, Kitae Yoo <kitaeyoo777@gmail.com>
Subject: [RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits
Date: Fri, 14 Aug 2026 20:46:47 +0900	[thread overview]
Message-ID: <20260814114649.51253-1-kitaeyoo777@gmail.com> (raw)

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)


             reply	other threads:[~2026-08-14 11:46 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-14 11:46 Kitae Yoo [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260814114649.51253-1-kitaeyoo777@gmail.com \
    --to=kitaeyoo777@gmail.com \
    --cc=jack@suse.com \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox