aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Zaborski <oshogbo@FreeBSD.org>2019-04-06 09:34:26 +0000
committerMariusz Zaborski <oshogbo@FreeBSD.org>2019-04-06 09:34:26 +0000
commita1304030b815cf1c7dbc934cbd994d8f333eb9cd (patch)
treea2167ee95d958b53b6c38b68b6965f8ae217db41
parent44b0f624aee0d43179bf3874c70e03b6ae49a4db (diff)
downloadsrc-a1304030b815cf1c7dbc934cbd994d8f333eb9cd.tar.gz
src-a1304030b815cf1c7dbc934cbd994d8f333eb9cd.zip
Introduce funlinkat syscall that always us to check if we are removing
the file associated with the given file descriptor. Reviewed by: kib, asomers Reviewed by: cem, jilles, brooks (they reviewed previous version) Discussed with: pjd, and many others Differential Revision: https://reviews.freebsd.org/D14567
Notes
Notes: svn path=/head/; revision=345982
-rw-r--r--include/unistd.h1
-rw-r--r--lib/libc/sys/Makefile.inc1
-rw-r--r--lib/libc/sys/Symbol.map1
-rw-r--r--lib/libc/sys/unlink.241
-rw-r--r--sys/cddl/compat/opensolaris/sys/vnode.h3
-rw-r--r--sys/compat/cloudabi/cloudabi_file.c6
-rw-r--r--sys/compat/freebsd32/syscalls.master2
-rw-r--r--sys/compat/linux/linux_file.c9
-rw-r--r--sys/kern/capabilities.conf1
-rw-r--r--sys/kern/syscalls.master8
-rw-r--r--sys/kern/vfs_mountroot.c2
-rw-r--r--sys/kern/vfs_syscalls.c115
-rw-r--r--sys/sys/fcntl.h10
-rw-r--r--sys/sys/syscallsubr.h4
-rw-r--r--sys/ufs/ffs/ffs_alloc.c7
15 files changed, 170 insertions, 41 deletions
diff --git a/include/unistd.h b/include/unistd.h
index 835a6dffc471..091ff16f4f92 100644
--- a/include/unistd.h
+++ b/include/unistd.h
@@ -585,6 +585,7 @@ off_t __syscall(quad_t, ...);
int undelete(const char *);
int unwhiteout(const char *);
void *valloc(size_t); /* obsoleted by malloc() */
+int funlinkat(int, const char *, int, int);
#ifndef _OPTRESET_DECLARED
#define _OPTRESET_DECLARED
diff --git a/lib/libc/sys/Makefile.inc b/lib/libc/sys/Makefile.inc
index bc8299eea8f2..22737fcb8949 100644
--- a/lib/libc/sys/Makefile.inc
+++ b/lib/libc/sys/Makefile.inc
@@ -485,6 +485,7 @@ MLINKS+=timer_settime.2 timer_getoverrun.2 \
MLINKS+=thr_kill.2 thr_kill2.2
MLINKS+=truncate.2 ftruncate.2
MLINKS+=unlink.2 unlinkat.2
+MLINKS+=unlink.2 funlinkat.2
MLINKS+=utimensat.2 futimens.2
MLINKS+=utimes.2 futimes.2 \
utimes.2 futimesat.2 \
diff --git a/lib/libc/sys/Symbol.map b/lib/libc/sys/Symbol.map
index a48c693575f8..74b68afdedef 100644
--- a/lib/libc/sys/Symbol.map
+++ b/lib/libc/sys/Symbol.map
@@ -406,6 +406,7 @@ FBSD_1.6 {
fhlinkat;
fhreadlink;
getfhat;
+ funlinkat;
};
FBSDprivate_1.0 {
diff --git a/lib/libc/sys/unlink.2 b/lib/libc/sys/unlink.2
index c6c6a150c1a2..83823f1d78ae 100644
--- a/lib/libc/sys/unlink.2
+++ b/lib/libc/sys/unlink.2
@@ -28,7 +28,7 @@
.\" @(#)unlink.2 8.1 (Berkeley) 6/4/93
.\" $FreeBSD$
.\"
-.Dd November 11, 2018
+.Dd April 6, 2019
.Dt UNLINK 2
.Os
.Sh NAME
@@ -42,7 +42,9 @@
.Ft int
.Fn unlink "const char *path"
.Ft int
-.Fn unlinkat "int fd" "const char *path" "int flag"
+.Fn unlinkat "int dfd" "const char *path" "int flag"
+.Ft int
+.Fn funlinkat "int dfd" "const char *path" "int fd" "int flag"
.Sh DESCRIPTION
The
.Fn unlink
@@ -74,7 +76,7 @@ except in the case where
specifies a relative path.
In this case the directory entry to be removed is determined
relative to the directory associated with the file descriptor
-.Fa fd
+.Fa dfd
instead of the current working directory.
.Pp
The values for
@@ -113,6 +115,26 @@ or
respectively, depending on whether or not the
.Dv AT_REMOVEDIR
bit is set in flag.
+.Pp
+The
+.Fn funlinkat
+system call can be used to unlink an already-opened file, unless that
+file has been replaced since it was opened.
+It is equivalent to
+.Fn unlinkat
+in the case where
+.Fa path
+is already open as the file descriptor
+.Fa fd .
+Otherwise, the path will not be removed and an error will be returned.
+The
+.Fa fd
+can be set the
+.Dv FD_NONE .
+In that case
+.Fn funlinkat
+behaves exactly like
+.Fn unlinkat .
.Sh RETURN VALUES
.Rv -std unlink
.Sh ERRORS
@@ -227,6 +249,15 @@ or the relative
.Fa path
escapes it.
.El
+.Pp
+In addition to the errors returned by
+.Fn unlinkat ,
+.Fn funlinkat
+may fail if:
+.Bl -tag -width Er
+.It Bq Er EDEADLK
+The file descriptor is not associated with the path.
+.El
.Sh SEE ALSO
.Xr chflags 2 ,
.Xr close 2 ,
@@ -246,6 +277,10 @@ The
.Fn unlinkat
system call appeared in
.Fx 8.0 .
+The
+.Fn funlinkat
+system call appeared in
+.Fx 13.0 .
.Pp
The
.Fn unlink
diff --git a/sys/cddl/compat/opensolaris/sys/vnode.h b/sys/cddl/compat/opensolaris/sys/vnode.h
index 54e78f2ffe9f..b337f96e9ae7 100644
--- a/sys/cddl/compat/opensolaris/sys/vnode.h
+++ b/sys/cddl/compat/opensolaris/sys/vnode.h
@@ -278,7 +278,8 @@ vn_remove(char *fnamep, enum uio_seg seg, enum rm dirflag)
ASSERT(seg == UIO_SYSSPACE);
ASSERT(dirflag == RMFILE);
- return (kern_unlinkat(curthread, AT_FDCWD, fnamep, seg, 0, 0));
+ return (kern_funlinkat(curthread, AT_FDCWD, fnamep, FD_NONE, seg, 0,
+ 0));
}
#endif /* _KERNEL */
diff --git a/sys/compat/cloudabi/cloudabi_file.c b/sys/compat/cloudabi/cloudabi_file.c
index 602ae5042962..0b5231bb885b 100644
--- a/sys/compat/cloudabi/cloudabi_file.c
+++ b/sys/compat/cloudabi/cloudabi_file.c
@@ -752,9 +752,11 @@ cloudabi_sys_file_unlink(struct thread *td,
return (error);
if (uap->flags & CLOUDABI_UNLINK_REMOVEDIR)
- error = kern_rmdirat(td, uap->fd, path, UIO_SYSSPACE, 0);
+ error = kern_frmdirat(td, uap->fd, path, FD_NONE,
+ UIO_SYSSPACE, 0);
else
- error = kern_unlinkat(td, uap->fd, path, UIO_SYSSPACE, 0, 0);
+ error = kern_funlinkat(td, uap->fd, path, FD_NONE,
+ UIO_SYSSPACE, 0, 0);
cloudabi_freestr(path);
return (error);
}
diff --git a/sys/compat/freebsd32/syscalls.master b/sys/compat/freebsd32/syscalls.master
index 26e2432c300a..94bea54d84c3 100644
--- a/sys/compat/freebsd32/syscalls.master
+++ b/sys/compat/freebsd32/syscalls.master
@@ -1145,5 +1145,7 @@
const char *to); }
567 AUE_NULL NOPROTO { int fhreadlink( struct fhandle *fhp, char *buf, \
size_t bufsize); }
+568 AUE_UNLINKAT NOPROTO { int funlinkat(int dfd, const char *path, int fd, \
+ int flag); }
; vim: syntax=off
diff --git a/sys/compat/linux/linux_file.c b/sys/compat/linux/linux_file.c
index ecc95370d4c1..e27f167aaba9 100644
--- a/sys/compat/linux/linux_file.c
+++ b/sys/compat/linux/linux_file.c
@@ -590,7 +590,7 @@ linux_unlink(struct thread *td, struct linux_unlink_args *args)
printf(ARGS(unlink, "%s"), path);
#endif
- error = kern_unlinkat(td, AT_FDCWD, path, UIO_SYSSPACE, 0, 0);
+ error = kern_funlinkat(td, AT_FDCWD, path, FD_NONE, UIO_SYSSPACE, 0, 0);
if (error == EPERM) {
/* Introduce POSIX noncompliant behaviour of Linux */
if (kern_statat(td, 0, AT_FDCWD, path, UIO_SYSSPACE, &st,
@@ -623,9 +623,10 @@ linux_unlinkat(struct thread *td, struct linux_unlinkat_args *args)
#endif
if (args->flag & LINUX_AT_REMOVEDIR)
- error = kern_rmdirat(td, dfd, path, UIO_SYSSPACE, 0);
+ error = kern_frmdirat(td, dfd, path, FD_NONE, UIO_SYSSPACE, 0);
else
- error = kern_unlinkat(td, dfd, path, UIO_SYSSPACE, 0, 0);
+ error = kern_funlinkat(td, dfd, path, FD_NONE, UIO_SYSSPACE, 0,
+ 0);
if (error == EPERM && !(args->flag & LINUX_AT_REMOVEDIR)) {
/* Introduce POSIX noncompliant behaviour of Linux */
if (kern_statat(td, AT_SYMLINK_NOFOLLOW, dfd, path,
@@ -741,7 +742,7 @@ linux_rmdir(struct thread *td, struct linux_rmdir_args *args)
if (ldebug(rmdir))
printf(ARGS(rmdir, "%s"), path);
#endif
- error = kern_rmdirat(td, AT_FDCWD, path, UIO_SYSSPACE, 0);
+ error = kern_frmdirat(td, AT_FDCWD, path, FD_NONE, UIO_SYSSPACE, 0);
LFREEPATH(path);
return (error);
}
diff --git a/sys/kern/capabilities.conf b/sys/kern/capabilities.conf
index b5198b7a341c..927f5f66d07e 100644
--- a/sys/kern/capabilities.conf
+++ b/sys/kern/capabilities.conf
@@ -468,6 +468,7 @@ readlinkat
renameat
symlinkat
unlinkat
+funlinkat
utimensat
##
diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master
index e9aae2c4c395..57af35814f64 100644
--- a/sys/kern/syscalls.master
+++ b/sys/kern/syscalls.master
@@ -3167,6 +3167,14 @@
size_t bufsize
);
}
+568 AUE_UNLINKAT STD {
+ int funlinkat(
+ int dfd,
+ _In_z_ const char *path,
+ int fd,
+ int flag
+ );
+ }
; Please copy any additions and changes to the following compatability tables:
; sys/compat/freebsd32/syscalls.master
diff --git a/sys/kern/vfs_mountroot.c b/sys/kern/vfs_mountroot.c
index 20ddeb63db2b..200015b4d016 100644
--- a/sys/kern/vfs_mountroot.c
+++ b/sys/kern/vfs_mountroot.c
@@ -389,7 +389,7 @@ vfs_mountroot_shuffle(struct thread *td, struct mount *mpdevfs)
if (mporoot == mpdevfs) {
vfs_unbusy(mpdevfs);
/* Unlink the no longer needed /dev/dev -> / symlink */
- error = kern_unlinkat(td, AT_FDCWD, "/dev/dev",
+ error = kern_funlinkat(td, AT_FDCWD, "/dev/dev", FD_NONE,
UIO_SYSSPACE, 0, 0);
if (error)
printf("mountroot: unable to unlink /dev/dev "
diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c
index ef01081601f2..49db5b350cd3 100644
--- a/sys/kern/vfs_syscalls.c
+++ b/sys/kern/vfs_syscalls.c
@@ -1751,7 +1751,22 @@ int
sys_unlink(struct thread *td, struct unlink_args *uap)
{
- return (kern_unlinkat(td, AT_FDCWD, uap->path, UIO_USERSPACE, 0, 0));
+ return (kern_funlinkat(td, AT_FDCWD, uap->path, FD_NONE, UIO_USERSPACE,
+ 0, 0));
+}
+
+static int
+kern_funlinkat_ex(struct thread *td, int dfd, const char *path, int fd,
+ int flag, enum uio_seg pathseg, ino_t oldinum)
+{
+
+ if ((flag & ~AT_REMOVEDIR) != 0)
+ return (EINVAL);
+
+ if ((flag & AT_REMOVEDIR) != 0)
+ return (kern_frmdirat(td, dfd, path, fd, UIO_USERSPACE, 0));
+
+ return (kern_funlinkat(td, dfd, path, fd, UIO_USERSPACE, 0, 0));
}
#ifndef _SYS_SYSPROTO_H_
@@ -1764,46 +1779,69 @@ struct unlinkat_args {
int
sys_unlinkat(struct thread *td, struct unlinkat_args *uap)
{
- int fd, flag;
- const char *path;
- flag = uap->flag;
- fd = uap->fd;
- path = uap->path;
+ return (kern_funlinkat_ex(td, uap->fd, uap->path, FD_NONE, uap->flag,
+ UIO_USERSPACE, 0));
+}
- if ((flag & ~(AT_REMOVEDIR | AT_BENEATH)) != 0)
- return (EINVAL);
+#ifndef _SYS_SYSPROTO_H_
+struct funlinkat_args {
+ int dfd;
+ const char *path;
+ int fd;
+ int flag;
+};
+#endif
+int
+sys_funlinkat(struct thread *td, struct funlinkat_args *uap)
+{
- if ((uap->flag & AT_REMOVEDIR) != 0)
- return (kern_rmdirat(td, fd, path, UIO_USERSPACE, flag));
- else
- return (kern_unlinkat(td, fd, path, UIO_USERSPACE, flag, 0));
+ return (kern_funlinkat_ex(td, uap->dfd, uap->path, uap->fd, uap->flag,
+ UIO_USERSPACE, 0));
}
int
-kern_unlinkat(struct thread *td, int fd, const char *path,
+kern_funlinkat(struct thread *td, int dfd, const char *path, int fd,
enum uio_seg pathseg, int flag, ino_t oldinum)
{
struct mount *mp;
+ struct file *fp;
struct vnode *vp;
struct nameidata nd;
struct stat sb;
+ cap_rights_t rights;
int error;
+ fp = NULL;
+ if (fd != FD_NONE) {
+ error = getvnode(td, fd, cap_rights_init(&rights, CAP_LOOKUP),
+ &fp);
+ if (error != 0)
+ return (error);
+ }
+
restart:
bwillwrite();
NDINIT_ATRIGHTS(&nd, DELETE, LOCKPARENT | LOCKLEAF | AUDITVNODE1 |
((flag & AT_BENEATH) != 0 ? BENEATH : 0),
- pathseg, path, fd, &cap_unlinkat_rights, td);
- if ((error = namei(&nd)) != 0)
- return (error == EINVAL ? EPERM : error);
+ pathseg, path, dfd, &cap_unlinkat_rights, td);
+ if ((error = namei(&nd)) != 0) {
+ if (error == EINVAL)
+ error = EPERM;
+ goto fdout;
+ }
vp = nd.ni_vp;
if (vp->v_type == VDIR && oldinum == 0) {
error = EPERM; /* POSIX */
} else if (oldinum != 0 &&
((error = vn_stat(vp, &sb, td->td_ucred, NOCRED, td)) == 0) &&
sb.st_ino != oldinum) {
- error = EIDRM; /* Identifier removed */
+ error = EIDRM; /* Identifier removed */
+ } else if (fp != NULL && fp->f_vnode != vp) {
+ if ((fp->f_vnode->v_iflag & VI_DOOMED) != 0)
+ error = EBADF;
+ else
+ error = EDEADLK;
} else {
/*
* The root of a mounted filesystem cannot be deleted.
@@ -1822,8 +1860,9 @@ restart:
else
vput(vp);
if ((error = vn_start_write(NULL, &mp,
- V_XSLEEP | PCATCH)) != 0)
- return (error);
+ V_XSLEEP | PCATCH)) != 0) {
+ goto fdout;
+ }
goto restart;
}
#ifdef MAC
@@ -1845,6 +1884,9 @@ out:
vrele(vp);
else
vput(vp);
+fdout:
+ if (fp != NULL)
+ fdrop(fp, td);
return (error);
}
@@ -3704,25 +3746,36 @@ int
sys_rmdir(struct thread *td, struct rmdir_args *uap)
{
- return (kern_rmdirat(td, AT_FDCWD, uap->path, UIO_USERSPACE, 0));
+ return (kern_frmdirat(td, AT_FDCWD, uap->path, FD_NONE, UIO_USERSPACE,
+ 0));
}
int
-kern_rmdirat(struct thread *td, int fd, const char *path, enum uio_seg pathseg,
- int flag)
+kern_frmdirat(struct thread *td, int dfd, const char *path, int fd,
+ enum uio_seg pathseg, int flag)
{
struct mount *mp;
struct vnode *vp;
+ struct file *fp;
struct nameidata nd;
+ cap_rights_t rights;
int error;
+ fp = NULL;
+ if (fd != FD_NONE) {
+ error = getvnode(td, fd, cap_rights_init(&rights, CAP_LOOKUP),
+ &fp);
+ if (error != 0)
+ return (error);
+ }
+
restart:
bwillwrite();
NDINIT_ATRIGHTS(&nd, DELETE, LOCKPARENT | LOCKLEAF | AUDITVNODE1 |
((flag & AT_BENEATH) != 0 ? BENEATH : 0),
- pathseg, path, fd, &cap_unlinkat_rights, td);
+ pathseg, path, dfd, &cap_unlinkat_rights, td);
if ((error = namei(&nd)) != 0)
- return (error);
+ goto fdout;
vp = nd.ni_vp;
if (vp->v_type != VDIR) {
error = ENOTDIR;
@@ -3742,6 +3795,15 @@ restart:
error = EBUSY;
goto out;
}
+
+ if (fp != NULL && fp->f_vnode != vp) {
+ if ((fp->f_vnode->v_iflag & VI_DOOMED) != 0)
+ error = EBADF;
+ else
+ error = EDEADLK;
+ goto out;
+ }
+
#ifdef MAC
error = mac_vnode_check_unlink(td->td_ucred, nd.ni_dvp, vp,
&nd.ni_cnd);
@@ -3756,7 +3818,7 @@ restart:
else
vput(nd.ni_dvp);
if ((error = vn_start_write(NULL, &mp, V_XSLEEP | PCATCH)) != 0)
- return (error);
+ goto fdout;
goto restart;
}
vfs_notify_upper(vp, VFS_NOTIFY_UPPER_UNLINK);
@@ -3769,6 +3831,9 @@ out:
vrele(nd.ni_dvp);
else
vput(nd.ni_dvp);
+fdout:
+ if (fp != NULL)
+ fdrop(fp, td);
return (error);
}
diff --git a/sys/sys/fcntl.h b/sys/sys/fcntl.h
index 04eab5e27ed1..b2e7f94199d0 100644
--- a/sys/sys/fcntl.h
+++ b/sys/sys/fcntl.h
@@ -316,6 +316,16 @@ struct __oflock {
#define POSIX_FADV_NOREUSE 5 /* access data only once */
#endif
+
+#ifdef __BSD_VISIBLE
+/*
+ * Magic value that specify that corresponding file descriptor to filename
+ * is unknown and sanitary check should be omitted in the funlinkat() and
+ * similar syscalls.
+ */
+#define FD_NONE -200
+#endif
+
#ifndef _KERNEL
__BEGIN_DECLS
int open(const char *, int, ...);
diff --git a/sys/sys/syscallsubr.h b/sys/sys/syscallsubr.h
index 3545745f5a7f..f82500087f08 100644
--- a/sys/sys/syscallsubr.h
+++ b/sys/sys/syscallsubr.h
@@ -218,7 +218,7 @@ int kern_recvit(struct thread *td, int s, struct msghdr *mp,
enum uio_seg fromseg, struct mbuf **controlp);
int kern_renameat(struct thread *td, int oldfd, const char *old, int newfd,
const char *new, enum uio_seg pathseg);
-int kern_rmdirat(struct thread *td, int fd, const char *path,
+int kern_frmdirat(struct thread *td, int dfd, const char *path, int fd,
enum uio_seg pathseg, int flag);
int kern_sched_getparam(struct thread *td, struct thread *targettd,
struct sched_param *param);
@@ -285,7 +285,7 @@ int kern_thr_new(struct thread *td, struct thr_param *param);
int kern_thr_suspend(struct thread *td, struct timespec *tsp);
int kern_truncate(struct thread *td, const char *path,
enum uio_seg pathseg, off_t length);
-int kern_unlinkat(struct thread *td, int fd, const char *path,
+int kern_funlinkat(struct thread *td, int dfd, const char *path, int fd,
enum uio_seg pathseg, int flag, ino_t oldinum);
int kern_utimesat(struct thread *td, int fd, const char *path,
enum uio_seg pathseg, struct timeval *tptr, enum uio_seg tptrseg);
diff --git a/sys/ufs/ffs/ffs_alloc.c b/sys/ufs/ffs/ffs_alloc.c
index f4bdd5789ab5..7882a86ebbce 100644
--- a/sys/ufs/ffs/ffs_alloc.c
+++ b/sys/ufs/ffs/ffs_alloc.c
@@ -3431,14 +3431,15 @@ sysctl_ffs_fsck(SYSCTL_HANDLER_ARGS)
}
#endif /* DEBUG */
/*
- * kern_unlinkat will do its own start/finish writes and
+ * kern_funlinkat will do its own start/finish writes and
* they do not nest, so drop ours here. Setting mp == NULL
* indicates that vn_finished_write is not needed down below.
*/
vn_finished_write(mp);
mp = NULL;
- error = kern_unlinkat(td, AT_FDCWD, (char *)(intptr_t)cmd.value,
- UIO_USERSPACE, 0, (ino_t)cmd.size);
+ error = kern_funlinkat(td, AT_FDCWD,
+ (char *)(intptr_t)cmd.value, FD_NONE, UIO_USERSPACE,
+ 0, (ino_t)cmd.size);
break;
case FFS_SET_INODE: