[RFC PATCH 0/2] quota: opt-in strict enforcement of project quota hard limits

Kitae Yoo <[email protected]>
Newsgroups org.kernel.vger.linux-ext4,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.