diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 71b9dc331aae..5c147edb6fff 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -44,6 +44,7 @@
 #include "object.h"
 #include "ruleset.h"
 #include "setup.h"
+#include "supervise.h"
 
 /* Underlying object management */
 
@@ -924,10 +925,13 @@ static bool is_access_to_paths_allowed(
 }
 
 static int current_check_access_path(const struct path *const path,
+				     struct dentry *const child,
 				     access_mask_t access_request)
 {
 	const struct landlock_ruleset *const dom = get_current_fs_domain();
 	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
+	bool is_remove = !!(access_request & (LANDLOCK_ACCESS_FS_REMOVE_FILE |
+					      LANDLOCK_ACCESS_FS_REMOVE_DIR));
 
 	if (!dom)
 		return 0;
@@ -938,6 +942,29 @@ static int current_check_access_path(const struct path *const path,
 				       NULL, 0, NULL, NULL))
 		return 0;
 
+	if (landlock_has_supervisors(dom)) {
+		layer_mask_t pending_ask_supervise_layers =
+			landlock_layer_masks_to_denied_layers(
+				access_request, layer_masks,
+				sizeof(layer_masks), dom->num_layers);
+
+		WARN_ON_ONCE(!pending_ask_supervise_layers);
+
+		struct path child_path = *path;
+		if (child) {
+			child_path.dentry = child;
+		}
+
+		bool supervisor_allowed = landlock_ask_supervised_layers(
+			dom, pending_ask_supervise_layers,
+			LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS, access_request,
+			&child_path, NULL, child && !is_remove, false, 0);
+
+		if (supervisor_allowed) {
+			return 0;
+		}
+	}
+
 	return -EACCES;
 }
 
@@ -1092,6 +1119,8 @@ static bool collect_domain_accesses(
  * - 0 if access is allowed;
  * - -EXDEV if @old_dentry would inherit new access rights from @new_dir;
  * - -EACCES if file removal or creation is denied.
+ *
+ * TODO: implement interation wiht supervisors.
  */
 static int current_check_refer_path(struct dentry *const old_dentry,
 				    const struct path *const new_dir,
@@ -1415,38 +1444,43 @@ static int hook_path_rename(const struct path *const old_dir,
 static int hook_path_mkdir(const struct path *const dir,
 			   struct dentry *const dentry, const umode_t mode)
 {
-	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR);
+	return current_check_access_path(dir, dentry,
+					 LANDLOCK_ACCESS_FS_MAKE_DIR);
 }
 
 static int hook_path_mknod(const struct path *const dir,
 			   struct dentry *const dentry, const umode_t mode,
 			   const unsigned int dev)
 {
-	return current_check_access_path(dir, get_mode_access(mode));
+	return current_check_access_path(dir, dentry, get_mode_access(mode));
 }
 
 static int hook_path_symlink(const struct path *const dir,
 			     struct dentry *const dentry,
 			     const char *const old_name)
 {
-	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM);
+	return current_check_access_path(dir, dentry,
+					 LANDLOCK_ACCESS_FS_MAKE_SYM);
 }
 
 static int hook_path_unlink(const struct path *const dir,
 			    struct dentry *const dentry)
 {
-	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE);
+	return current_check_access_path(dir, dentry,
+					 LANDLOCK_ACCESS_FS_REMOVE_FILE);
 }
 
 static int hook_path_rmdir(const struct path *const dir,
 			   struct dentry *const dentry)
 {
-	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR);
+	return current_check_access_path(dir, dentry,
+					 LANDLOCK_ACCESS_FS_REMOVE_DIR);
 }
 
 static int hook_path_truncate(const struct path *const path)
 {
-	return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
+	return current_check_access_path(path, NULL,
+					 LANDLOCK_ACCESS_FS_TRUNCATE);
 }
 
 /* File hooks */
@@ -1562,9 +1596,81 @@ static int hook_file_open(struct file *const file)
 	if ((open_access_request & allowed_access) == open_access_request)
 		return 0;
 
+	if (landlock_has_supervisors(dom)) {
+		layer_mask_t pending_ask_supervise_layers =
+			landlock_layer_masks_to_denied_layers(
+				open_access_request, layer_masks,
+				sizeof(layer_masks), dom->num_layers);
+
+		WARN_ON_ONCE(!pending_ask_supervise_layers);
+
+		/*
+		 * We don't need to ask the supervisor for optional
+		 * access right now - we can ask later.
+		 */
+
+		bool supervisor_allowed = landlock_ask_supervised_layers(
+			dom, pending_ask_supervise_layers,
+			LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS,
+			open_access_request, &file->f_path, NULL, false, false,
+			0);
+
+		if (supervisor_allowed) {
+			landlock_file(file)->allowed_access =
+				open_access_request;
+			return 0;
+		}
+	}
+
 	return -EACCES;
 }
 
+/*
+ * For any "optional" permissions (truncate and ioctl) which was
+ * not allowed at time a file was opened, we want to check with
+ * any supervised layers if they actually allow it at the time
+ * the user tries to do such an operation on the opened fd.  We
+ * can check for access on the path (using the opener's domain)
+ * as the opener can never re-gain permissions under landlock.
+ */
+static bool check_opened_file_access_supervisor(struct file *const file,
+						access_mask_t access_request)
+{
+	const struct landlock_ruleset *dom = landlock_get_applicable_domain(
+		landlock_cred(file->f_cred)->domain, any_fs);
+
+	if (landlock_has_supervisors(dom)) {
+		layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
+		bool allowed = is_access_to_paths_allowed(
+			dom, &file->f_path,
+			landlock_init_layer_masks(dom, access_request,
+						  &layer_masks,
+						  LANDLOCK_KEY_INODE),
+			&layer_masks, NULL, 0, NULL, NULL);
+		if (allowed) {
+			WARN_ONCE(
+				1,
+				"Access was previously not allowed, now it's allowed in the same domain. Landlock bug?");
+			return false;
+		}
+
+		layer_mask_t pending_ask_supervise_layers =
+			landlock_layer_masks_to_denied_layers(
+				access_request, layer_masks,
+				sizeof(layer_masks), dom->num_layers);
+		WARN_ON_ONCE(!pending_ask_supervise_layers);
+
+		bool supervisor_allowed = landlock_ask_supervised_layers(
+			dom, pending_ask_supervise_layers,
+			LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS, access_request,
+			&file->f_path, NULL, false, false, 0);
+
+		return supervisor_allowed;
+	}
+
+	return false;
+}
+
 static int hook_file_truncate(struct file *const file)
 {
 	/*
@@ -1579,6 +1685,12 @@ static int hook_file_truncate(struct file *const file)
 	 */
 	if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE)
 		return 0;
+
+	if (check_opened_file_access_supervisor(file,
+						LANDLOCK_ACCESS_FS_TRUNCATE)) {
+		return 0;
+	}
+
 	return -EACCES;
 }
 
@@ -1602,6 +1714,11 @@ static int hook_file_ioctl(struct file *file, unsigned int cmd,
 	if (is_masked_device_ioctl(cmd))
 		return 0;
 
+	if (check_opened_file_access_supervisor(file,
+						LANDLOCK_ACCESS_FS_IOCTL_DEV)) {
+		return 0;
+	}
+
 	return -EACCES;
 }
 
@@ -1625,6 +1742,11 @@ static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
 	if (is_masked_device_ioctl_compat(cmd))
 		return 0;
 
+	if (check_opened_file_access_supervisor(file,
+						LANDLOCK_ACCESS_FS_IOCTL_DEV)) {
+		return 0;
+	}
+
 	return -EACCES;
 }
 
diff --git a/security/landlock/supervise.c b/security/landlock/supervise.c
index a3bb6928f453..3f31a89c4c96 100644
--- a/security/landlock/supervise.c
+++ b/security/landlock/supervise.c
@@ -12,6 +12,12 @@
 
 #include "supervise.h"
 
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+
+#define pr_fmt(fmt) "landlock-supervise: " fmt
+
 struct landlock_supervisor *landlock_create_supervisor(void)
 {
 	struct landlock_supervisor *supervisor;
@@ -70,3 +76,119 @@ void landlock_put_supervisor(struct landlock_supervisor *const supervisor)
 		kfree(supervisor);
 	}
 }
+
+/**
+ * landlock_ask_supervised_layers - check if all denied layers
+ * are supervised, and if yes, ask all of them for permission.
+ *
+ * Return whether access should be allowed.  If denied_layers
+ * contains any non-supervised layer, will return false without
+ * making any supervisor event.
+ *
+ * Caller owns any paths passed in, we might get refs.
+ */
+bool landlock_ask_supervised_layers(
+	const struct landlock_ruleset *const domain,
+	const layer_mask_t denied_layers,
+	const landlock_supervise_event_type_t request_type,
+	const access_mask_t access_request, const struct path *const path1,
+	const struct path *const path2, const bool path1_new,
+	const bool path2_new, const __u16 port)
+{
+	size_t layer_level;
+	unsigned long denied_layers_ = denied_layers;
+
+	if (WARN_ON_ONCE(!denied_layers)) {
+		return true;
+	}
+
+	for_each_set_bit(layer_level, &denied_layers_, domain->num_layers) {
+		if (!domain->layer_stack[layer_level].supervisor) {
+			return false;
+		}
+	}
+
+	/*
+	 * All denied layers are supervisor layers, so we just ask
+	 * them in turn. There's good argument for either order (top
+	 * -> bottom, or the other way), so we just do the easiest
+	 * thing here.
+	 */
+
+	for_each_set_bit(layer_level, &denied_layers_, domain->num_layers) {
+		struct landlock_supervisor *const supervisor =
+			domain->layer_stack[layer_level].supervisor;
+
+		/*
+		 * supervisor will stay valid here because we're blocking
+		 * this thread which references the layer, which in terms
+		 * references the supervisor.
+		 */
+
+		/* TODO: memchg supervisor owner then allocate with account */
+		struct landlock_supervise_event_kernel *event __free(
+			landlock_put_supervise_event) =
+			kzalloc(sizeof(*event), GFP_KERNEL_ACCOUNT);
+
+		int rc;
+
+		if (!event) {
+			pr_alert(
+				"failed to allocate memory for supervisor event\n");
+			return false;
+		}
+
+		refcount_set(&event->usage, 1);
+		event->state = LANDLOCK_SUPERVISE_EVENT_NEW;
+
+		event->type = request_type;
+		event->access_request = access_request;
+		event->accessor = get_pid(task_pid(current));
+		switch (request_type) {
+		case LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS:
+			if (path1) {
+				path_get(path1);
+				event->target_1 = *path1;
+				event->target_1_is_new = path1_new;
+			}
+			if (path2) {
+				path_get(path2);
+				event->target_2 = *path2;
+				event->target_2_is_new = path2_new;
+			}
+			break;
+		case LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS:
+			event->port = port;
+			break;
+		}
+
+		if (WARN_ON(!supervisor)) {
+			/*
+			 * We checked all denied layers are supervised
+			 * earlier...
+			 */
+			return false;
+		}
+
+		spin_lock(&supervisor->lock);
+		event->event_id = supervisor->next_event_id++;
+		landlock_get_supervise_event(event);
+		list_add_tail(&event->node, &supervisor->event_queue);
+		spin_unlock(&supervisor->lock);
+		wake_up(&supervisor->poll_event_wq);
+
+		rc = wait_var_event_killable(
+			event, LANDLOCK_SUPERVISE_EVENT_HANDLED(event));
+		if (rc) {
+			/* Task died, doesn't matter what we say */
+			return false;
+		}
+		if (event->state != LANDLOCK_SUPERVISE_EVENT_ALLOWED) {
+			return false;
+		}
+
+		/* event has __free */
+	}
+
+	return true;
+}
diff --git a/security/landlock/supervise.h b/security/landlock/supervise.h
index febe26a11578..10fc274fabb7 100644
--- a/security/landlock/supervise.h
+++ b/security/landlock/supervise.h
@@ -12,6 +12,7 @@
 #include <linux/wait.h>
 #include <linux/path.h>
 #include <linux/pid.h>
+#include <uapi/linux/landlock.h>
 
 #include "access.h"
 #include "ruleset.h"
@@ -46,9 +47,56 @@ struct landlock_supervise_event_kernel {
 	refcount_t usage;
 	enum landlock_supervise_event_state state;
 
-	/* more fields to come */
+	/* Cookie as presented to user-space */
+	u32 event_id;
+
+	landlock_supervise_event_type_t type;
+	access_mask_t access_request;
+	struct pid *accessor;
+	union {
+		struct {
+			/**
+			 * @target_1: The first (and may be the only, for
+			 * most requests) target path. To expose as much
+			 * useful information to the supervisor as possible,
+			 * for file creation and deletion, this points to the
+			 * actual path being created (or deleted), rather
+			 * than the parent directory. Note that for the
+			 * create case, this means that the dentry will be
+			 * negative (unless we end up in some horrible race).
+			 * In the create case, target_1_is_new is set, so
+			 * that we know to pass the parent as the fd to the
+			 * user-space supervisor, and fill destname with the
+			 * name of the file.
+			 *
+			 * For refer (link and rename), this points to the
+			 * source (or simply the first argument in case of
+			 * exchange) being linked. It will necessarily have
+			 * to be an existing file (even though the dentry may
+			 * turn negative).
+			 */
+			struct path target_1;
+			/**
+			 * @target_2: The destination path for link and
+			 * rename (or simply the second argument in case of
+			 * exchange). target_2_is_new will be set unless this
+			 * is an exchange.
+			 */
+			struct path target_2;
+
+			u8 target_1_is_new : 1;
+			u8 target_2_is_new : 1;
+		};
+		struct {
+			__u16 port;
+		};
+	};
 };
 
+#define LANDLOCK_SUPERVISE_EVENT_HANDLED(event)                \
+	((event)->state == LANDLOCK_SUPERVISE_EVENT_ALLOWED || \
+	 (event)->state == LANDLOCK_SUPERVISE_EVENT_DENIED)
+
 struct landlock_supervisor *landlock_create_supervisor(void);
 void landlock_get_supervisor(struct landlock_supervisor *const supervisor);
 void landlock_put_supervisor(struct landlock_supervisor *const supervisor);
@@ -62,8 +110,62 @@ static inline void landlock_get_supervise_event(
 static inline void landlock_put_supervise_event(
 	struct landlock_supervise_event_kernel *const event)
 {
-	if (refcount_dec_and_test(&event->usage))
+	if (refcount_dec_and_test(&event->usage)) {
+		switch (event->type) {
+		case LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS:
+			if (event->target_1.dentry)
+				path_put(&event->target_1);
+			if (event->target_2.dentry)
+				path_put(&event->target_2);
+			break;
+		case LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS:
+			break;
+		}
+		put_pid(event->accessor);
 		kfree(event);
+	}
+}
+
+DEFINE_FREE(landlock_put_supervise_event,
+	    struct landlock_supervise_event_kernel *,
+	    if (_T) landlock_put_supervise_event(_T))
+
+static inline bool
+landlock_has_supervisors(const struct landlock_ruleset *const domain)
+{
+	size_t layer_level;
+	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
+		if (domain->layer_stack[layer_level].supervisor)
+			return true;
+	}
+	return false;
 }
 
+static inline layer_mask_t landlock_layer_masks_to_denied_layers(
+	const access_mask_t access_request, const layer_mask_t layer_masks[],
+	const size_t masks_array_size, const int num_layers)
+{
+	unsigned long access_req = access_request;
+	layer_mask_t denied_layers = 0;
+	size_t layer_level;
+	unsigned long access_bit;
+
+	for (layer_level = 0; layer_level < num_layers; layer_level++) {
+		for_each_set_bit(access_bit, &access_req, masks_array_size) {
+			if (layer_masks[access_bit] & BIT_ULL(layer_level))
+				denied_layers |= BIT_ULL(layer_level);
+		}
+	}
+
+	return denied_layers;
+}
+
+bool landlock_ask_supervised_layers(
+	const struct landlock_ruleset *const domain,
+	const layer_mask_t denied_layers,
+	const landlock_supervise_event_type_t request_type,
+	const access_mask_t access_request, const struct path *const path1,
+	const struct path *const path2, const bool path1_new,
+	const bool path2_new, const __u16 port);
+
 #endif /* _SECURITY_LANDLOCK_SUPERVISE_H */
