diff --git a/fs/namei.c b/fs/namei.c
index 79b0ff9b151e..6efdd0b8e2b1 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2331,6 +2331,9 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
 		error = nd_jump_root(nd);
 		if (unlikely(error))
 			return ERR_PTR(error);
+		error = security_resolve_path_at(&nd->path, NULL, flags);
+		if (error)
+			return ERR_PTR(error);
 		return s;
 	}
 
@@ -2350,6 +2353,9 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
 			get_fs_pwd(current->fs, &nd->path);
 			nd->inode = nd->path.dentry->d_inode;
 		}
+		error = security_resolve_path_at(&nd->path, NULL, flags);
+		if (error)
+			return ERR_PTR(error);
 	} else {
 		/* Caller must check execute permissions on the starting path component */
 		struct fd f = fdget_raw(nd->dfd);
@@ -2373,7 +2379,10 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
 			path_get(&nd->path);
 			nd->inode = nd->path.dentry->d_inode;
 		}
+		error = security_resolve_path_at(&nd->path, f.file, flags);
 		fdput(f);
+		if (error)
+			return ERR_PTR(error);
 	}
 
 	/* For scoped-lookups we need to set the root to the dirfd as well. */
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 04c01794de83..1db6d53b1dfb 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -178,6 +178,8 @@ LSM_HOOK(int, 0, file_send_sigiotask, struct task_struct *tsk,
 	 struct fown_struct *fown, int sig)
 LSM_HOOK(int, 0, file_receive, struct file *file)
 LSM_HOOK(int, 0, file_open, struct file *file)
+LSM_HOOK(int, 0, resolve_path_at, const struct path *path_at,
+	 struct file *file_at, int lookup_flags)
 LSM_HOOK(int, 0, task_alloc, struct task_struct *task,
 	 unsigned long clone_flags)
 LSM_HOOK(void, LSM_RET_VOID, task_free, struct task_struct *task)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 5c4c5c0602cb..2ef130e36309 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -600,6 +600,14 @@
  *	Save open-time permission checking state for later use upon
  *	file_permission, and recheck access if anything has changed
  *	since inode_permission.
+ * @resolve_path_at:
+ *	Check path resolution from a file descriptor, or the current working
+ *      directory, or the current root directory.
+ *      Can be called in RCU read-side critical section.
+ *      @path_at points to the base path.
+ *      @file_at can point to the file descriptor used to resolve the path, or
+ *      be NULL for AT_FDCWD.
+ *      @lookup_flags contains the lookup options (e.g. LOOKUP_IS_SCOPED).
  *
  * Security hooks for task operations.
  *
diff --git a/include/linux/security.h b/include/linux/security.h
index 06f7c50ce77f..e3ae93b9189d 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -391,6 +391,8 @@ int security_file_send_sigiotask(struct task_struct *tsk,
 				 struct fown_struct *fown, int sig);
 int security_file_receive(struct file *file);
 int security_file_open(struct file *file);
+int security_resolve_path_at(const struct path *path_at, struct file *file_at,
+		int lookup_flags);
 int security_task_alloc(struct task_struct *task, unsigned long clone_flags);
 void security_task_free(struct task_struct *task);
 int security_cred_alloc_blank(struct cred *cred, gfp_t gfp);
@@ -1011,6 +1013,13 @@ static inline int security_file_open(struct file *file)
 	return 0;
 }
 
+static inline int security_resolve_path_at(const struct path *path_at,
+					   struct file *file_at,
+					   int lookup_flags)
+{
+	return 0;
+}
+
 static inline int security_task_alloc(struct task_struct *task,
 				      unsigned long clone_flags)
 {
diff --git a/security/security.c b/security/security.c
index b38155b2de83..98161f5919ee 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1637,6 +1637,12 @@ int security_file_open(struct file *file)
 	return fsnotify_perm(file, MAY_OPEN);
 }
 
+int security_resolve_path_at(const struct path *path_at, struct file *file_at,
+		int lookup_flags)
+{
+	return call_int_hook(resolve_path_at, 0, path_at, file_at, lookup_flags);
+}
+
 int security_task_alloc(struct task_struct *task, unsigned long clone_flags)
 {
 	int rc = lsm_task_alloc(task);
