Index: coda/coda_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/coda/coda_vnops.c,v
retrieving revision 1.117
diff -u -p -u -r1.117 coda_vnops.c
--- coda/coda_vnops.c	5 Dec 2021 08:10:39 -0000	1.117
+++ coda/coda_vnops.c	27 Mar 2022 01:10:02 -0000
@@ -1198,6 +1198,10 @@ coda_link(void *v)
 	error = EFAULT;		/* XXX better value */
 	goto exit;
     }
+    error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_ADD_LINK, vp,
+	dvp, 0);
+    if (error)
+	    goto exit;
     error = venus_link(vtomi(vp), &cp->c_fid, &dcp->c_fid, nm, len, cred, l);
     VOP_UNLOCK(vp);
 
Index: fs/tmpfs/tmpfs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/fs/tmpfs/tmpfs_vnops.c,v
retrieving revision 1.148
diff -u -p -u -r1.148 tmpfs_vnops.c
--- fs/tmpfs/tmpfs_vnops.c	20 Oct 2021 03:08:17 -0000	1.148
+++ fs/tmpfs/tmpfs_vnops.c	27 Mar 2022 01:10:03 -0000
@@ -801,6 +801,11 @@ tmpfs_link(void *v)
 		goto out;
 	}
 
+	error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_ADD_LINK, vp,
+	    dvp, 0);
+	if (error)
+		goto out;
+
 	/* Allocate a new directory entry to represent the inode. */
 	error = tmpfs_alloc_dirent(VFS_TO_TMPFS(vp->v_mount),
 	    cnp->cn_nameptr, cnp->cn_namelen, &de);
Index: fs/udf/udf_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/fs/udf/udf_vnops.c,v
retrieving revision 1.119
diff -u -p -u -r1.119 udf_vnops.c
--- fs/udf/udf_vnops.c	16 Feb 2022 22:00:56 -0000	1.119
+++ fs/udf/udf_vnops.c	27 Mar 2022 01:10:03 -0000
@@ -1559,18 +1559,21 @@ udf_do_link(struct vnode *dvp, struct vn
 	udf_node = VTOI(vp);
 
 	error = VOP_GETATTR(vp, &vap, FSCRED);
-	if (error) {
-		VOP_UNLOCK(vp);
-		return error;
-	}
+	if (error)
+		goto out;
 
 	/* check link count overflow */
 	if (vap.va_nlink >= (1<<16)-1) {	/* uint16_t */
-		VOP_UNLOCK(vp);
-		return EMLINK;
+		error = EMLINK;
+		goto out;
 	}
+	error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_ADD_LINK, vp,
+	    dvp, NULL, 0);
+	if (error)
+		goto out;
 
 	error = udf_dir_attach(dir_node->ump, dir_node, udf_node, &vap, cnp);
+out:
 	if (error)
 		VOP_UNLOCK(vp);
 	return error;
Index: fs/unionfs/unionfs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/fs/unionfs/unionfs_vnops.c,v
retrieving revision 1.17
diff -u -p -u -r1.17 unionfs_vnops.c
--- fs/unionfs/unionfs_vnops.c	20 Oct 2021 03:08:17 -0000	1.17
+++ fs/unionfs/unionfs_vnops.c	27 Mar 2022 01:10:03 -0000
@@ -566,7 +566,6 @@ unionfs_close_abort:
 static int
 unionfs_check_corrected_access(u_short mode, struct vattr *va, kauth_cred_t cred)
 {
-	int		result;
 	int		error;
 	uid_t		uid;	/* upper side vnode's uid */
 	gid_t		gid;	/* upper side vnode's gid */
@@ -590,10 +589,7 @@ unionfs_check_corrected_access(u_short m
 	}
 
 	/* check group */
-	error = kauth_cred_ismember_gid(cred, gid, &result);
-	if (error != 0)
-		return error;
-	if (result) {
+	if (kauth_cred_groupmember(cred, gid) == 0) {
 		if (mode & VEXEC)
 			mask |= S_IXGRP;
 		if (mode & VREAD)
Index: fs/v7fs/v7fs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/fs/v7fs/v7fs_vnops.c,v
retrieving revision 1.34
diff -u -p -u -r1.34 v7fs_vnops.c
--- fs/v7fs/v7fs_vnops.c	11 Feb 2022 10:55:15 -0000	1.34
+++ fs/v7fs/v7fs_vnops.c	27 Mar 2022 01:10:03 -0000
@@ -745,21 +745,27 @@ v7fs_link(void *v)
 	struct v7fs_inode *p = &node->inode;
 	struct v7fs_self *fs = node->v7fsmount->core;
 	struct componentname *cnp = a->a_cnp;
-	int error = 0;
+	int error, abrt = 1;
 
 	DPRINTF("%p\n", vp);
-	/* Lock soruce file */
+	/* Lock source file */
 	if ((error = vn_lock(vp, LK_EXCLUSIVE))) {
 		DPRINTF("lock failed. %p\n", vp);
-		VOP_ABORTOP(dvp, cnp);
 		goto unlock;
 	}
+	error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_ADD_LINK, vp,
+	    dvp, NULL, 0);
+	if (error)
+		goto unlock;
+	abrt = 0;
 	error = v7fs_file_link(fs, parent, p, cnp->cn_nameptr, cnp->cn_namelen);
 	/* Sync dirent size change. */
 	uvm_vnp_setsize(dvp, v7fs_inode_filesize(&parent_node->inode));
 
 	VOP_UNLOCK(vp);
 unlock:
+	if (abrt)
+		VOP_ABORTOP(dvp, cnp);
 	return error;
 }
 
Index: kern/kern_auth.c
===================================================================
RCS file: /cvsroot/src/sys/kern/kern_auth.c,v
retrieving revision 1.79
diff -u -p -u -r1.79 kern_auth.c
--- kern/kern_auth.c	12 Mar 2022 15:32:32 -0000	1.79
+++ kern/kern_auth.c	27 Mar 2022 01:10:03 -0000
@@ -404,6 +404,25 @@ kauth_cred_ismember_gid(kauth_cred_t cre
 	return (0);
 }
 
+int
+kauth_cred_groupmember(kauth_cred_t cred, gid_t gid)
+{
+	int ismember, error;
+
+	KASSERT(cred != NULL);
+	KASSERT(cred != NOCRED);
+	KASSERT(cred != FSCRED);
+
+	error = kauth_cred_ismember_gid(cred, gid, &ismember);
+	if (error)
+		return error;
+
+	if (kauth_cred_getegid(cred) == gid || ismember)
+		return 0;
+
+	return -1;
+}
+
 u_int
 kauth_cred_ngroups(kauth_cred_t cred)
 {
Index: kern/sysv_ipc.c
===================================================================
RCS file: /cvsroot/src/sys/kern/sysv_ipc.c,v
retrieving revision 1.41
diff -u -p -u -r1.41 sysv_ipc.c
--- kern/sysv_ipc.c	21 Feb 2020 00:26:22 -0000	1.41
+++ kern/sysv_ipc.c	27 Mar 2022 01:10:03 -0000
@@ -258,7 +258,6 @@ sysvipc_listener_cb(kauth_cred_t cred, k
     void *arg0, void *arg1, void *arg2, void *arg3)
 {
 	mode_t mask;
-	int ismember = 0;
 	struct ipc_perm *perm;
 	int mode;
 	enum kauth_system_req req;
@@ -290,10 +289,8 @@ sysvipc_listener_cb(kauth_cred_t cred, k
 		return ((perm->mode & mask) == mask ? KAUTH_RESULT_ALLOW : KAUTH_RESULT_DEFER /* EACCES */);
 	}
 
-	if (kauth_cred_getegid(cred) == perm->gid ||
-	    (kauth_cred_ismember_gid(cred, perm->gid, &ismember) == 0 && ismember) ||
-	    kauth_cred_getegid(cred) == perm->cgid ||
-	    (kauth_cred_ismember_gid(cred, perm->cgid, &ismember) == 0 && ismember)) {
+	if (kauth_cred_groupmember(cred, perm->gid) == 0 ||
+	    kauth_cred_groupmember(cred, perm->cgid) == 0) {
 		if (mode & IPC_R)
 			mask |= S_IRGRP;
 		if (mode & IPC_W)
Index: miscfs/genfs/genfs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/miscfs/genfs/genfs_vnops.c,v
retrieving revision 1.217
diff -u -p -u -r1.217 genfs_vnops.c
--- miscfs/genfs/genfs_vnops.c	19 Mar 2022 13:52:45 -0000	1.217
+++ miscfs/genfs/genfs_vnops.c	27 Mar 2022 01:10:03 -0000
@@ -663,18 +663,6 @@ genfs_node_wrlocked(struct vnode *vp)
 	return rw_write_held(&gp->g_glock);
 }
 
-static int
-groupmember(gid_t gid, kauth_cred_t cred)
-{
-	int ismember;
-	int error = kauth_cred_ismember_gid(cred, gid, &ismember);
-	if (error)
-		return error;
-	if (kauth_cred_getegid(cred) == gid || ismember)
-		return 0;
-	return -1;
-}
-
 /*
  * Common filesystem object access control check routine.  Accepts a
  * vnode, cred, uid, gid, mode, acl, requested access mode.
@@ -712,7 +700,7 @@ genfs_can_access(vnode_t *vp, kauth_cred
 
 	/* Otherwise, check the groups (first match) */
 	/* Otherwise, check the groups. */
-	error = groupmember(file_gid, cred);
+	error = kauth_cred_groupmember(cred, file_gid);
 	if (error > 0)
 		return error;
 	if (error == 0) {
@@ -864,7 +852,7 @@ genfs_can_access_acl_posix1e(vnode_t *vp
 		struct acl_entry *ae = &acl->acl_entry[i];
 		switch (ae->ae_tag) {
 		case ACL_GROUP_OBJ:
-			error = groupmember(file_gid, cred);
+			error = kauth_cred_groupmember(cred, file_gid);
 			if (error > 0)
 				return error;
 			if (error)
@@ -885,7 +873,7 @@ genfs_can_access_acl_posix1e(vnode_t *vp
 			break;
 
 		case ACL_GROUP:
-			error = groupmember(ae->ae_id, cred);
+			error = kauth_cred_groupmember(cred, ae->ae_id);
 			if (error > 0)
 				return error;
 			if (error)
@@ -919,7 +907,7 @@ genfs_can_access_acl_posix1e(vnode_t *vp
 			struct acl_entry *ae = &acl->acl_entry[i];
 			switch (ae->ae_tag) {
 			case ACL_GROUP_OBJ:
-				error = groupmember(file_gid, cred);
+				error = kauth_cred_groupmember(cred, file_gid);
 				if (error > 0)
 					return error;
 				if (error)
@@ -935,7 +923,7 @@ genfs_can_access_acl_posix1e(vnode_t *vp
 				goto out;
 
 			case ACL_GROUP:
-				error = groupmember(ae->ae_id, cred);
+				error = kauth_cred_groupmember(cred, ae->ae_id);
 				if (error > 0)
 					return error;
 				if (error)
@@ -1053,14 +1041,14 @@ _acl_denies(const struct acl *aclp, int 
 				continue;
 			break;
 		case ACL_GROUP_OBJ:
-			error = groupmember(file_gid, cred);
+			error = kauth_cred_groupmember(cred, file_gid);
 			if (error > 0)
 				return error;
 			if (error != 0)
 				continue;
 			break;
 		case ACL_GROUP:
-			error = groupmember(ae->ae_id, cred);
+			error = kauth_cred_groupmember(cred, ae->ae_id);
 			if (error > 0)
 				return error;
 			if (error != 0)
Index: nfs/nfs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/nfs/nfs_vnops.c,v
retrieving revision 1.321
diff -u -p -u -r1.321 nfs_vnops.c
--- nfs/nfs_vnops.c	20 Oct 2021 03:08:18 -0000	1.321
+++ nfs/nfs_vnops.c	27 Mar 2022 01:10:03 -0000
@@ -2012,6 +2012,13 @@ nfs_link(void *v)
 		return error;
 	}
 
+	error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_ADD_LINK, vp,
+	    dvp, 0);
+	if (error) {
+		VOP_ABORTOP(dvp, cnp);
+		return error;
+	}
+
 	/*
 	 * Push all writes to the server, so that the attribute cache
 	 * doesn't get "out of sync" with the server.
Index: secmodel/extensions/secmodel_extensions.c
===================================================================
RCS file: /cvsroot/src/sys/secmodel/extensions/secmodel_extensions.c,v
retrieving revision 1.12
diff -u -p -u -r1.12 secmodel_extensions.c
--- secmodel/extensions/secmodel_extensions.c	16 Mar 2020 21:20:12 -0000	1.12
+++ secmodel/extensions/secmodel_extensions.c	27 Mar 2022 01:10:03 -0000
@@ -49,12 +49,14 @@ MODULE(MODULE_CLASS_SECMODEL, extensions
 static int dovfsusermount;
 static int curtain;
 static int user_set_cpu_affinity;
+static int hardlink_check_uid;
+static int hardlink_check_gid;
 
 #ifdef PT_SETDBREGS
 int user_set_dbregs;
 #endif
 
-static kauth_listener_t l_system, l_process, l_network;
+static kauth_listener_t l_system, l_process, l_network, l_vnode;
 
 static secmodel_t extensions_sm;
 
@@ -73,6 +75,8 @@ static int secmodel_extensions_process_c
     void *, void *, void *, void *, void *);
 static int secmodel_extensions_network_cb(kauth_cred_t, kauth_action_t,
     void *, void *, void *, void *, void *);
+static int secmodel_extensions_vnode_cb(kauth_cred_t, kauth_action_t,
+    void *, void *, void *, void *, void *);
 
 SYSCTL_SETUP(sysctl_security_extensions_setup,
     "security extensions sysctl")
@@ -150,6 +154,25 @@ SYSCTL_SETUP(sysctl_security_extensions_
 		       CTL_CREATE, CTL_EOL);
 #endif
 
+	sysctl_createv(clog, 0, &rnode, NULL,
+		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
+		       CTLTYPE_INT, "hardlink_check_uid",
+		       SYSCTL_DESCR("Whether unprivileged users can hardlink "\
+			    "to files they don't own"),
+		       sysctl_extensions_user_handler, 0,
+		       &hardlink_check_uid, 0,
+		       CTL_CREATE, CTL_EOL);
+
+	sysctl_createv(clog, 0, &rnode, NULL,
+		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
+		       CTLTYPE_INT, "hardlink_check_uid",
+		       SYSCTL_DESCR("Whether unprivileged users can hardlink "\
+			    "to files they that they are not in their " \
+			    "group membership"),
+		       sysctl_extensions_user_handler, 0,
+		       &hardlink_check_gid, 0,
+		       CTL_CREATE, CTL_EOL);
+
 	/* Compatibility: vfs.generic.usermount */
 	sysctl_createv(clog, 0, NULL, NULL,
 		       CTLFLAG_PERMANENT,
@@ -280,6 +303,8 @@ secmodel_extensions_start(void)
 	    secmodel_extensions_process_cb, NULL);
 	l_network = kauth_listen_scope(KAUTH_SCOPE_NETWORK,
 	    secmodel_extensions_network_cb, NULL);
+	l_vnode = kauth_listen_scope(KAUTH_SCOPE_VNODE,
+	    secmodel_extensions_vnode_cb, NULL);
 }
 
 static void
@@ -289,6 +314,7 @@ secmodel_extensions_stop(void)
 	kauth_unlisten_scope(l_system);
 	kauth_unlisten_scope(l_process);
 	kauth_unlisten_scope(l_network);
+	kauth_unlisten_scope(l_vnode);
 }
 
 static int
@@ -503,3 +529,26 @@ secmodel_extensions_network_cb(kauth_cre
 
 	return (result);
 }
+
+static int
+secmodel_extensions_vnode_cb(kauth_cred_t cred, kauth_action_t action,
+    void *cookie, void *arg0, void *arg1, void *arg2, void *arg3)
+{
+	int error;
+	struct vattr va;
+
+	if ((action & KAUTH_VNODE_ADD_LINK) == 0)
+		return KAUTH_RESULT_DEFER;
+
+	error = VOP_GETATTR((vnode_t *)arg0, &va, cred);
+	if (error)
+		return KAUTH_RESULT_DENY;
+
+	if (hardlink_check_uid && kauth_cred_geteuid(cred) != va.va_uid)
+		return KAUTH_RESULT_DENY;
+
+	if (hardlink_check_gid && kauth_cred_groupmember(cred, va.va_gid) != 0)
+		return KAUTH_RESULT_DENY;
+
+	return KAUTH_RESULT_DEFER;
+}
Index: sys/kauth.h
===================================================================
RCS file: /cvsroot/src/sys/sys/kauth.h,v
retrieving revision 1.86
diff -u -p -u -r1.86 kauth.h
--- sys/kauth.h	8 Sep 2020 14:12:57 -0000	1.86
+++ sys/kauth.h	27 Mar 2022 01:10:03 -0000
@@ -51,7 +51,7 @@ enum uio_seg;
 /* Types. */
 typedef struct kauth_scope     *kauth_scope_t;
 typedef struct kauth_listener  *kauth_listener_t;
-typedef uint32_t		kauth_action_t;
+typedef uint64_t		kauth_action_t;
 typedef int (*kauth_scope_callback_t)(kauth_cred_t, kauth_action_t,
 				      void *, void *, void *, void *, void *);
 typedef	struct kauth_key       *kauth_key_t;
@@ -383,36 +383,37 @@ enum {
 /*
  * Vnode scope - action bits.
  */
-#define	KAUTH_VNODE_READ_DATA		(1U << 0)
+#define	KAUTH_VNODE_READ_DATA		(1ULL << 0)
 #define	KAUTH_VNODE_LIST_DIRECTORY	KAUTH_VNODE_READ_DATA
-#define	KAUTH_VNODE_WRITE_DATA		(1U << 1)
+#define	KAUTH_VNODE_WRITE_DATA		(1ULL << 1)
 #define	KAUTH_VNODE_ADD_FILE		KAUTH_VNODE_WRITE_DATA
-#define	KAUTH_VNODE_EXECUTE		(1U << 2)
+#define	KAUTH_VNODE_EXECUTE		(1ULL << 2)
 #define	KAUTH_VNODE_SEARCH		KAUTH_VNODE_EXECUTE
-#define	KAUTH_VNODE_DELETE		(1U << 3)
-#define	KAUTH_VNODE_APPEND_DATA		(1U << 4)
+#define	KAUTH_VNODE_DELETE		(1ULL << 3)
+#define	KAUTH_VNODE_APPEND_DATA		(1ULL << 4)
 #define	KAUTH_VNODE_ADD_SUBDIRECTORY	KAUTH_VNODE_APPEND_DATA
-#define	KAUTH_VNODE_READ_TIMES		(1U << 5)
-#define	KAUTH_VNODE_WRITE_TIMES		(1U << 6)
-#define	KAUTH_VNODE_READ_FLAGS		(1U << 7)
-#define	KAUTH_VNODE_WRITE_FLAGS		(1U << 8)
-#define	KAUTH_VNODE_READ_SYSFLAGS	(1U << 9)
-#define	KAUTH_VNODE_WRITE_SYSFLAGS	(1U << 10)
-#define	KAUTH_VNODE_RENAME		(1U << 11)
-#define	KAUTH_VNODE_CHANGE_OWNERSHIP	(1U << 12)
-#define	KAUTH_VNODE_READ_SECURITY	(1U << 13)
-#define	KAUTH_VNODE_WRITE_SECURITY	(1U << 14)
-#define	KAUTH_VNODE_READ_ATTRIBUTES	(1U << 15)
-#define	KAUTH_VNODE_WRITE_ATTRIBUTES	(1U << 16)
-#define	KAUTH_VNODE_READ_EXTATTRIBUTES	(1U << 17)
-#define	KAUTH_VNODE_WRITE_EXTATTRIBUTES	(1U << 18)
-#define	KAUTH_VNODE_RETAIN_SUID		(1U << 19)
-#define	KAUTH_VNODE_RETAIN_SGID		(1U << 20)
-#define	KAUTH_VNODE_REVOKE		(1U << 21)
-
-#define	KAUTH_VNODE_IS_EXEC		(1U << 29)
-#define	KAUTH_VNODE_HAS_SYSFLAGS	(1U << 30)
-#define	KAUTH_VNODE_ACCESS		(1U << 31)
+#define	KAUTH_VNODE_READ_TIMES		(1ULL << 5)
+#define	KAUTH_VNODE_WRITE_TIMES		(1ULL << 6)
+#define	KAUTH_VNODE_READ_FLAGS		(1ULL << 7)
+#define	KAUTH_VNODE_WRITE_FLAGS		(1ULL << 8)
+#define	KAUTH_VNODE_READ_SYSFLAGS	(1ULL << 9)
+#define	KAUTH_VNODE_WRITE_SYSFLAGS	(1ULL << 10)
+#define	KAUTH_VNODE_RENAME		(1ULL << 11)
+#define	KAUTH_VNODE_CHANGE_OWNERSHIP	(1ULL << 12)
+#define	KAUTH_VNODE_READ_SECURITY	(1ULL << 13)
+#define	KAUTH_VNODE_WRITE_SECURITY	(1ULL << 14)
+#define	KAUTH_VNODE_READ_ATTRIBUTES	(1ULL << 15)
+#define	KAUTH_VNODE_WRITE_ATTRIBUTES	(1ULL << 16)
+#define	KAUTH_VNODE_READ_EXTATTRIBUTES	(1ULL << 17)
+#define	KAUTH_VNODE_WRITE_EXTATTRIBUTES	(1ULL << 18)
+#define	KAUTH_VNODE_RETAIN_SUID		(1ULL << 19)
+#define	KAUTH_VNODE_RETAIN_SGID		(1ULL << 20)
+#define	KAUTH_VNODE_REVOKE		(1ULL << 21)
+
+#define	KAUTH_VNODE_IS_EXEC		(1ULL << 29)
+#define	KAUTH_VNODE_HAS_SYSFLAGS	(1ULL << 30)
+#define	KAUTH_VNODE_ACCESS		(1ULL << 31)
+#define	KAUTH_VNODE_ADD_LINK		(1ULL << 32)
 
 /*
  * This is a special fs_decision indication that can be used by file-systems
@@ -494,6 +495,7 @@ gid_t kauth_cred_getgid(kauth_cred_t);
 gid_t kauth_cred_getegid(kauth_cred_t);
 gid_t kauth_cred_getsvgid(kauth_cred_t);
 int kauth_cred_ismember_gid(kauth_cred_t, gid_t, int *);
+int kauth_cred_groupmember(kauth_cred_t, gid_t);
 u_int kauth_cred_ngroups(kauth_cred_t);
 gid_t kauth_cred_group(kauth_cred_t, u_int);
 
Index: sys/param.h
===================================================================
RCS file: /cvsroot/src/sys/sys/param.h,v
retrieving revision 1.708
diff -u -p -u -r1.708 param.h
--- sys/param.h	19 Mar 2022 13:53:32 -0000	1.708
+++ sys/param.h	27 Mar 2022 01:10:03 -0000
@@ -67,7 +67,7 @@
  *	2.99.9		(299000900)
  */
 
-#define	__NetBSD_Version__	999009500	/* NetBSD 9.99.95 */
+#define	__NetBSD_Version__	999009600	/* NetBSD 9.99.96 */
 
 #define __NetBSD_Prereq__(M,m,p) (((((M) * 100000000) + \
     (m) * 1000000) + (p) * 100) <= __NetBSD_Version__)
Index: ufs/chfs/chfs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/ufs/chfs/chfs_vnops.c,v
retrieving revision 1.47
diff -u -p -u -r1.47 chfs_vnops.c
--- ufs/chfs/chfs_vnops.c	7 Dec 2021 21:37:37 -0000	1.47
+++ ufs/chfs/chfs_vnops.c	27 Mar 2022 01:10:03 -0000
@@ -1081,32 +1081,36 @@ chfs_link(void *v)
 	struct componentname *cnp = ((struct vop_link_v2_args *) v)->a_cnp;
 
 	struct chfs_inode *ip, *parent;
-	int error = 0;
+	int error, abrt = 1;
 
 	if (vp->v_type == VDIR) {
-		VOP_ABORTOP(dvp, cnp);
 		error = EISDIR;
 		goto out;
 	}
 	if (dvp->v_mount != vp->v_mount) {
-		VOP_ABORTOP(dvp, cnp);
 		error = EXDEV;
 		goto out;
 	}
-	if (dvp != vp && (error = vn_lock(vp, LK_EXCLUSIVE))) {
-		VOP_ABORTOP(dvp, cnp);
+	if (dvp != vp && (error = vn_lock(vp, LK_EXCLUSIVE)))
+		goto out;
+
+	error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_ADD_LINK, vp,
+	    dvp, NULL, 0);
+	if (error)
 		goto out;
-	}
 
 	parent = VTOI(dvp);
 	ip = VTOI(vp);
 
+	abrt = 0;
 	error = chfs_do_link(ip,
 	    parent, cnp->cn_nameptr, cnp->cn_namelen, ip->ch_type);
 
 	if (dvp != vp)
 		VOP_UNLOCK(vp);
 out:
+	if (abrt)
+		VOP_ABORTOP(dvp, cnp);
 	return error;
 }
 
Index: ufs/ext2fs/ext2fs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/ufs/ext2fs/ext2fs_vnops.c,v
retrieving revision 1.136
diff -u -p -u -r1.136 ext2fs_vnops.c
--- ufs/ext2fs/ext2fs_vnops.c	20 Oct 2021 03:08:19 -0000	1.136
+++ ufs/ext2fs/ext2fs_vnops.c	27 Mar 2022 01:10:03 -0000
@@ -580,7 +580,7 @@ ext2fs_link(void *v)
 	struct vnode *vp = ap->a_vp;
 	struct componentname *cnp = ap->a_cnp;
 	struct inode *ip;
-	int error;
+	int error, abrt = 1;
 	struct ufs_lookup_results *ulr;
 
 	KASSERT(dvp != vp);
@@ -592,23 +592,24 @@ ext2fs_link(void *v)
 	UFS_CHECK_CRAPCOUNTER(VTOI(dvp));
 
 	error = vn_lock(vp, LK_EXCLUSIVE);
-	if (error) {
-		VOP_ABORTOP(dvp, cnp);
+	if (error)
 		goto out2;
-	}
+	error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_ADD_LINK, vp,
+	    dvp, 0);
+	if (error)
+		goto out1;
 	ip = VTOI(vp);
 	if ((nlink_t)ip->i_e2fs_nlink >= EXT2FS_LINK_MAX) {
-		VOP_ABORTOP(dvp, cnp);
 		error = EMLINK;
 		goto out1;
 	}
 	if (ip->i_e2fs_flags & (EXT2_IMMUTABLE | EXT2_APPEND)) {
-		VOP_ABORTOP(dvp, cnp);
 		error = EPERM;
 		goto out1;
 	}
 	ip->i_e2fs_nlink++;
 	ip->i_flag |= IN_CHANGE;
+	abrt = 0;
 	error = ext2fs_update(vp, NULL, NULL, UPDATE_WAIT);
 	if (!error)
 		error = ext2fs_direnter(ip, dvp, ulr, cnp);
@@ -619,6 +620,8 @@ ext2fs_link(void *v)
 out1:
 	VOP_UNLOCK(vp);
 out2:
+	if (abrt)
+		VOP_ABORTOP(dvp, cnp);
 	return error;
 }
 
Index: ufs/lfs/ulfs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/ufs/lfs/ulfs_vnops.c,v
retrieving revision 1.55
diff -u -p -u -r1.55 ulfs_vnops.c
--- ufs/lfs/ulfs_vnops.c	20 Oct 2021 03:08:19 -0000	1.55
+++ ufs/lfs/ulfs_vnops.c	27 Mar 2022 01:10:03 -0000
@@ -561,7 +561,7 @@ ulfs_link(void *v)
 	struct vnode *vp = ap->a_vp;
 	struct componentname *cnp = ap->a_cnp;
 	struct inode *ip;
-	int error;
+	int error, abrt = 1;
 	struct ulfs_lookup_results *ulr;
 
 	KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
@@ -573,26 +573,26 @@ ulfs_link(void *v)
 	ULFS_CHECK_CRAPCOUNTER(VTOI(dvp));
 
 	error = vn_lock(vp, LK_EXCLUSIVE);
-	if (error) {
-		VOP_ABORTOP(dvp, cnp);
+	if (error)
 		goto out2;
-	}
 	if (vp->v_mount != dvp->v_mount) {
 		error = ENOENT;
-		VOP_ABORTOP(dvp, cnp);
 		goto out2;
 	}
 	ip = VTOI(vp);
 	if ((nlink_t)ip->i_nlink >= LINK_MAX) {
-		VOP_ABORTOP(dvp, cnp);
 		error = EMLINK;
 		goto out1;
 	}
 	if (ip->i_flags & (IMMUTABLE | APPEND)) {
-		VOP_ABORTOP(dvp, cnp);
 		error = EPERM;
 		goto out1;
 	}
+	error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_ADD_LINK, vp,
+	    dvp, 0);
+	if (error)
+		goto out1;
+	abrt = 0;
 	ip->i_nlink++;
 	DIP_ASSIGN(ip, nlink, ip->i_nlink);
 	ip->i_state |= IN_CHANGE;
@@ -608,6 +608,8 @@ ulfs_link(void *v)
 	}
  out1:
 	VOP_UNLOCK(vp);
+	if (abrt)
+		VOP_ABORTOP(dvp, cnp);
  out2:
 	return (error);
 }
Index: ufs/ufs/ufs_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/ufs/ufs/ufs_vnops.c,v
retrieving revision 1.261
diff -u -p -u -r1.261 ufs_vnops.c
--- ufs/ufs/ufs_vnops.c	26 Nov 2021 17:35:12 -0000	1.261
+++ ufs/ufs/ufs_vnops.c	27 Mar 2022 01:10:04 -0000
@@ -893,7 +893,7 @@ ufs_link(void *v)
 	struct mount *mp = dvp->v_mount;
 	struct inode *ip;
 	struct direct *newdir;
-	int error;
+	int error, abrt = 1;
 	struct ufs_lookup_results *ulr;
 
 	KASSERT(dvp != vp);
@@ -905,29 +905,32 @@ ufs_link(void *v)
 	UFS_CHECK_CRAPCOUNTER(VTOI(dvp));
 
 	error = vn_lock(vp, LK_EXCLUSIVE);
-	if (error) {
-		VOP_ABORTOP(dvp, cnp);
+	if (error)
 		goto out2;
-	}
+
 	ip = VTOI(vp);
 	if ((nlink_t)ip->i_nlink >= LINK_MAX) {
-		VOP_ABORTOP(dvp, cnp);
 		error = EMLINK;
 		goto out1;
 	}
 	if (ip->i_flags & (IMMUTABLE | APPEND)) {
-		VOP_ABORTOP(dvp, cnp);
 		error = EPERM;
 		goto out1;
 	}
+
+	error = kauth_authorize_vnode(cnp->cn_cred, KAUTH_VNODE_ADD_LINK, vp,
+	    dvp, 0);
+	if (error)
+		goto out1;
+
 	error = UFS_WAPBL_BEGIN(mp);
-	if (error) {
-		VOP_ABORTOP(dvp, cnp);
+	if (error)
 		goto out1;
-	}
+
 	ip->i_nlink++;
 	DIP_ASSIGN(ip, nlink, ip->i_nlink);
 	ip->i_flag |= IN_CHANGE;
+	abrt = 0;
 	error = UFS_UPDATE(vp, NULL, NULL, UPDATE_DIROP);
 	if (!error) {
 		newdir = pool_cache_get(ufs_direct_cache, PR_WAITOK);
@@ -945,6 +948,8 @@ ufs_link(void *v)
  out1:
 	VOP_UNLOCK(vp);
  out2:
+	if (abrt)
+		VOP_ABORTOP(dvp, cnp);
 	return (error);
 }