From 1b4b75171ee3f2213b7671878a910fd5ddb3306e Mon Sep 17 00:00:00 2001 From: Konstantin Belousov Date: Sun, 18 Sep 2022 14:46:19 +0300 Subject: [PATCH] Add vn_rlimit_fsizex() and vn_rlimit_fsizex_res() The vn_rlimit_fsizex() function: - checks that the write does not exceed RLIMIT_FSIZE limit and fs maximum supported file size - truncates write length if it exceeds the RLIMIT_FSIZE or max file size, but there are some bytes to write - sends SIGXFSZ if RLIMIT_FSIZE would be exceed otherwise POSIX mandates the truncated write in case when some bytes can be written but whole write request fails the RLIMIT_FSIZE check. The function is supposed to be used from VOP_WRITE()s. Due to pecularity in the VFS generic write syscall layer, uio_resid must correctly reflect the written amount (noted by markj). Provide the dual vn_rlimit_fsizex_res() function to correct uio_resid after the clamp done in vn_rlimit_fsizex() on VOP_WRITE() return. PR: 164793 Reviewed by: asomers, jah, markj Tested by: pho Sponsored by: The FreeBSD Foundation MFC after: 2 weeks Differential revision: https://reviews.freebsd.org/D36625 --- sys/kern/vfs_vnops.c | 92 ++++++++++++++++++++++++++++++++++++++------ sys/sys/vnode.h | 3 ++ 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c index 6eef39a37f25..21f3f99a6741 100644 --- a/sys/kern/vfs_vnops.c +++ b/sys/kern/vfs_vnops.c @@ -2389,39 +2389,107 @@ vn_rlimit_trunc(u_quad_t size, struct thread *td) return (EFBIG); } -int -vn_rlimit_fsize(const struct vnode *vp, const struct uio *uio, - struct thread *td) +static int +vn_rlimit_fsizex1(const struct vnode *vp, struct uio *uio, off_t maxfsz, + bool adj, struct thread *td) { off_t lim; bool ktr_write; - if (td == NULL) + if (vp->v_type != VREG) return (0); /* - * There are conditions where the limit is to be ignored. - * However, since it is almost never reached, check it first. + * Handle file system maximum file size. + */ + if (maxfsz != 0 && uio->uio_offset + uio->uio_resid > maxfsz) { + if (!adj || uio->uio_offset >= maxfsz) + return (EFBIG); + uio->uio_resid = maxfsz - uio->uio_offset; + } + + /* + * This is kernel write (e.g. vnode_pager) or accounting + * write, ignore limit. + */ + if (td == NULL || (td->td_pflags2 & TDP2_ACCT) != 0) + return (0); + + /* + * Calculate file size limit. */ ktr_write = (td->td_pflags & TDP_INKTRACE) != 0; - lim = lim_cur(td, RLIMIT_FSIZE); - if (__predict_false(ktr_write)) - lim = td->td_ktr_io_lim; + lim = __predict_false(ktr_write) ? td->td_ktr_io_lim : + lim_cur(td, RLIMIT_FSIZE); + + /* + * Is the limit reached? + */ if (__predict_true((uoff_t)uio->uio_offset + uio->uio_resid <= lim)) return (0); /* - * The limit is reached. + * Prepared filesystems can handle writes truncated to the + * file size limit. */ - if (vp->v_type != VREG || - (td->td_pflags2 & TDP2_ACCT) != 0) + if (adj && (uoff_t)uio->uio_offset < lim) { + uio->uio_resid = lim - (uoff_t)uio->uio_offset; return (0); + } if (!ktr_write || ktr_filesize_limit_signal) vn_send_sigxfsz(td->td_proc); return (EFBIG); } +/* + * Helper for VOP_WRITE() implementations, the common code to + * handle maximum supported file size on the filesystem, and + * RLIMIT_FSIZE, except for special writes from accounting subsystem + * and ktrace. + * + * For maximum file size (maxfsz argument): + * - return EFBIG if uio_offset is beyond it + * - otherwise, clamp uio_resid if write would extend file beyond maxfsz. + * + * For RLIMIT_FSIZE: + * - return EFBIG and send SIGXFSZ if uio_offset is beyond the limit + * - otherwise, clamp uio_resid if write would extend file beyond limit. + * + * If clamping occured, the adjustment for uio_resid is stored in + * *resid_adj, to be re-applied by vn_rlimit_fsizex_res() on return + * from the VOP. + */ +int +vn_rlimit_fsizex(const struct vnode *vp, struct uio *uio, off_t maxfsz, + ssize_t *resid_adj, struct thread *td) +{ + ssize_t resid_orig; + int error; + bool adj; + + resid_orig = uio->uio_resid; + adj = resid_adj != NULL; + error = vn_rlimit_fsizex1(vp, uio, maxfsz, adj, td); + if (adj) + *resid_adj = resid_orig - uio->uio_resid; + return (error); +} + +void +vn_rlimit_fsizex_res(struct uio *uio, ssize_t resid_adj) +{ + uio->uio_resid += resid_adj; +} + +int +vn_rlimit_fsize(const struct vnode *vp, const struct uio *uio, + struct thread *td) +{ + return (vn_rlimit_fsizex(vp, __DECONST(struct uio *, uio), 0, NULL, + td)); +} + int vn_chmod(struct file *fp, mode_t mode, struct ucred *active_cred, struct thread *td) diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h index da17170cbb2d..63b3c23bd072 100644 --- a/sys/sys/vnode.h +++ b/sys/sys/vnode.h @@ -786,6 +786,9 @@ int vn_rdwr_inchunks(enum uio_rw rw, struct vnode *vp, void *base, int vn_read_from_obj(struct vnode *vp, struct uio *uio); int vn_rlimit_fsize(const struct vnode *vp, const struct uio *uio, struct thread *td); +int vn_rlimit_fsizex(const struct vnode *vp, struct uio *uio, + off_t maxfsz, ssize_t *resid_adj, struct thread *td); +void vn_rlimit_fsizex_res(struct uio *uio, ssize_t resid_adj); int vn_rlimit_trunc(u_quad_t size, struct thread *td); int vn_start_write(struct vnode *vp, struct mount **mpp, int flags); int vn_start_secondary_write(struct vnode *vp, struct mount **mpp,