Index: lib/libc/sys/fcntl.2 =================================================================== --- lib/libc/sys/fcntl.2 +++ lib/libc/sys/fcntl.2 @@ -28,7 +28,7 @@ .\" @(#)fcntl.2 8.2 (Berkeley) 1/12/94 .\" $FreeBSD$ .\" -.Dd Nov 15, 2018 +.Dd August 24, 2019 .Dt FCNTL 2 .Os .Sh NAME @@ -180,6 +180,13 @@ A zero value in .Fa arg turns off read ahead. +.It Dv F_ADD_SEALS +Add seals to the file as described below, if the underlying filesystem supports +seals. +.It Dv F_GET_SEALS +Get seals associated with descriptor +.Fa fd , +if the underlying filesystem supports seals. .El .Pp The flags for the @@ -217,6 +224,32 @@ upon availability of data to be read. .El .Pp +The seals that may be applied with +.Dv F_ADD_SEALS +are as follows: +.Bl -tag -width F_SEAL_SHRINK +.It Dv F_SEAL_SEAL +Prevent any further seals from being applied to the file. +.It Dv F_SEAL_SHRINK +Prevent the file from being shrunk with +.Xr ftruncate 2 . +.It Dv F_SEAL_GROW +Prevent the file from being enlarged with +.Xr ftruncate 2 . +.It Dv F_SEAL_WRITE +Prevent any further +.Xr write 2 +calls to the file. +.El +.Pp +Seals are on a per-inode basis and require support by the underlying filesystem. +If the underlying filesystem does not support seals, +.Dv F_ADD_SEALS +and +.Dv F_GET_SEALS +will error out with +.Dv EINVAL . +.Pp Several commands are available for doing advisory file locking; they all operate on the following structure: .Bd -literal @@ -565,6 +598,14 @@ .Pp The argument .Fa cmd +is +.Dv F_ADD_SEALS +or +.Dv F_GET_SEALS , +and the underlying filesystem does not support sealing. +.Pp +The argument +.Fa cmd is invalid. .It Bq Er EMFILE The argument Index: lib/libc/sys/truncate.2 =================================================================== --- lib/libc/sys/truncate.2 +++ lib/libc/sys/truncate.2 @@ -28,7 +28,7 @@ .\" @(#)truncate.2 8.1 (Berkeley) 6/4/93 .\" $FreeBSD$ .\" -.Dd May 4, 2015 +.Dd August 24, 2019 .Dt TRUNCATE 2 .Os .Sh NAME @@ -144,6 +144,8 @@ .Fa fd descriptor is not open for writing. +.It Bq Er ENOTCAPABLE +The file descriptor being operated on has insufficient rights. .El .Sh SEE ALSO .Xr chflags 2 , Index: sys/kern/kern_descrip.c =================================================================== --- sys/kern/kern_descrip.c +++ sys/kern/kern_descrip.c @@ -46,6 +46,7 @@ #include #include +#include #include #include #include @@ -483,15 +484,17 @@ int kern_fcntl(struct thread *td, int fd, int cmd, intptr_t arg) { + cap_rights_t rights; struct filedesc *fdp; struct flock *flp; struct file *fp, *fp2; struct filedescent *fde; struct proc *p; struct vnode *vp; - int error, flg, tmp; + int error, flg, seals, tmp; uint64_t bsize; off_t foffset; + bool setcaps; error = 0; flg = F_POSIX; @@ -756,6 +759,69 @@ fdrop(fp, td); break; + case F_ADD_SEALS: + error = fget_unlocked(fdp, fd, &cap_no_rights, &fp, NULL); + if (error != 0) + break; + if ((fp->f_flag & (FWRITE | FSEALABLE)) != (FWRITE | FSEALABLE)) + error = EINVAL; + else if (fo_get_seals(fp, &seals) != 0) + error = EINVAL; + else if ((seals & F_SEAL_SEAL) != 0) + error = EPERM; + if (error != 0) { + fdrop(fp, td); + break; + } + + tmp = (arg & ~seals); + /* Nop; the rest isn't worth evaluating */ + if (tmp == 0) { + fdrop(fp, td); + break; + } + + /* This shouldn't fail; if it does, we can't act on this seal */ + error = fo_add_seals(fp, arg); + if (error != 0) { + fdrop(fp, td); + break; + } + + rights = *cap_rights(fdp, fd); + setcaps = false; + + /* F_SEAL_WRITE translates to revoking CAP_WRITE */ + if ((tmp & F_SEAL_WRITE) != 0) { + cap_rights_clear(&rights, CAP_WRITE); + setcaps = true; + } + + /* We can revoke truncate completely */ + if ((tmp & F_SEAL_TRUNCATE) != 0 && + (seals & F_SEAL_TRUNCATE) == F_SEAL_TRUNCATE) { + cap_rights_clear(&rights, CAP_FTRUNCATE); + setcaps = true; + } + + if (setcaps) + kern_cap_rights_limit(td, fd, &rights); + + fdrop(fp, td); + break; + + case F_GET_SEALS: + error = fget_unlocked(fdp, fd, &cap_no_rights, &fp, NULL); + if (error != 0) + break; + if ((fp->f_flag & FSEALABLE) != 0 && + fo_get_seals(fp, &seals) == 0) + td->td_retval[0] = seals; + else + error = EINVAL; + fdrop(fp, td); + break; + case F_RDAHEAD: arg = arg ? 128 * 1024: 0; /* FALLTHROUGH */ Index: sys/kern/sys_generic.c =================================================================== --- sys/kern/sys_generic.c +++ sys/kern/sys_generic.c @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -595,8 +596,9 @@ int kern_ftruncate(struct thread *td, int fd, off_t length) { + struct stat sb; struct file *fp; - int error; + int error, seals; AUDIT_ARG_FD(fd); if (length < 0) @@ -606,10 +608,30 @@ return (error); AUDIT_ARG_FILE(td->td_proc, fp); if (!(fp->f_flag & FWRITE)) { - fdrop(fp, td); - return (EINVAL); + error = EINVAL; + goto out; + } + /* + * ftruncate being completely sealed is already handled by revoking the + * ftruncate capability completely. Here we only have to handle the + * case of either restricted shrink or restricted growth, but not both. + */ + if (fo_get_seals(fp, &seals) == 0 && (seals & F_SEAL_TRUNCATE) != 0) { + error = fo_stat(fp, &sb, td->td_ucred, td); + if (error == 0) { + if (length < sb.st_size && + (seals & F_SEAL_SHRINK) != 0) { + error = ENOTCAPABLE; + goto out; + } else if (length > sb.st_size && + (seals & F_SEAL_GROW) != 0) { + error = ENOTCAPABLE; + goto out; + } + } } error = fo_truncate(fp, length, td->td_ucred, td); +out: fdrop(fp, td); return (error); } Index: sys/kern/uipc_shm.c =================================================================== --- sys/kern/uipc_shm.c +++ sys/kern/uipc_shm.c @@ -131,6 +131,8 @@ static fo_seek_t shm_seek; static fo_fill_kinfo_t shm_fill_kinfo; static fo_mmap_t shm_mmap; +static fo_get_seals_t shm_get_seals; +static fo_add_seals_t shm_add_seals; /* File descriptor operations. */ struct fileops shm_ops = { @@ -148,6 +150,8 @@ .fo_seek = shm_seek, .fo_fill_kinfo = shm_fill_kinfo, .fo_mmap = shm_mmap, + .fo_get_seals = shm_get_seals, + .fo_add_seals = shm_add_seals, .fo_flags = DFLAG_PASSABLE | DFLAG_SEEKABLE }; @@ -1143,6 +1147,26 @@ return (res); } +static int +shm_add_seals(struct file *fp, int seals) +{ + struct shmfd *shmfd; + + shmfd = fp->f_data; + atomic_set_int(&shmfd->shm_seals, seals); + return (0); +} + +static int +shm_get_seals(struct file *fp, int *seals) +{ + struct shmfd *shmfd; + + shmfd = fp->f_data; + *seals = shmfd->shm_seals; + return (0); +} + static int sysctl_posix_shm_list(SYSCTL_HANDLER_ARGS) { Index: sys/sys/fcntl.h =================================================================== --- sys/sys/fcntl.h +++ sys/sys/fcntl.h @@ -196,6 +196,10 @@ #define FRDAHEAD O_CREAT #endif +#ifdef _KERNEL +#define FSEALABLE O_TRUNC +#endif + #if __POSIX_VISIBLE >= 200809 /* * Magic value that specify the use of the current working directory @@ -248,8 +252,22 @@ #endif #if __BSD_VISIBLE #define F_DUP2FD_CLOEXEC 18 /* Like F_DUP2FD, but FD_CLOEXEC is set */ +#define F_ADD_SEALS 19 +#define F_GET_SEALS 20 #endif +/* Seals (F_ADD_SEALS, F_GET_SEALS */ +#if __BSD_VISIBLE +#define F_SEAL_SEAL 0x0001 /* Prevent adding sealings */ +#define F_SEAL_SHRINK 0x0002 /* May not shrink */ +#define F_SEAL_GROW 0x0004 /* May not grow */ +#define F_SEAL_WRITE 0x0008 /* May not write */ + +#ifdef _KERNEL +#define F_SEAL_TRUNCATE (F_SEAL_SHRINK | F_SEAL_GROW) +#endif /* _KERNEL */ +#endif /* __BSD_VISIBLE */ + /* file descriptor flags (F_GETFD, F_SETFD) */ #define FD_CLOEXEC 1 /* close-on-exec flag */ Index: sys/sys/file.h =================================================================== --- sys/sys/file.h +++ sys/sys/file.h @@ -123,6 +123,8 @@ vm_size_t size, vm_prot_t prot, vm_prot_t cap_maxprot, int flags, vm_ooffset_t foff, struct thread *td); typedef int fo_aio_queue_t(struct file *fp, struct kaiocb *job); +typedef int fo_add_seals_t(struct file *fp, int flags); +typedef int fo_get_seals_t(struct file *fp, int *flags); typedef int fo_flags_t; struct fileops { @@ -141,6 +143,8 @@ fo_fill_kinfo_t *fo_fill_kinfo; fo_mmap_t *fo_mmap; fo_aio_queue_t *fo_aio_queue; + fo_add_seals_t *fo_add_seals; + fo_get_seals_t *fo_get_seals; fo_flags_t fo_flags; /* DFLAG_* below */ }; @@ -426,6 +430,26 @@ return ((*fp->f_ops->fo_aio_queue)(fp, job)); } +static __inline int +fo_add_seals(struct file *fp, int seals) +{ + + if (fp->f_ops->fo_add_seals == NULL) + return (ENODEV); + return ((*fp->f_ops->fo_add_seals)(fp, seals)); +} + +static __inline int +fo_get_seals(struct file *fp, int *seals) +{ + + if (fp->f_ops->fo_get_seals == NULL) + return (ENODEV); + if (seals == NULL) + return (EINVAL); + return ((*fp->f_ops->fo_get_seals)(fp, seals)); +} + #endif /* _KERNEL */ #endif /* !SYS_FILE_H */ Index: sys/sys/mman.h =================================================================== --- sys/sys/mman.h +++ sys/sys/mman.h @@ -238,6 +238,8 @@ struct rangelock shm_rl; struct mtx shm_mtx; + + int shm_seals; }; #endif