diff --git a/tools/testing/selftests/bpf/prog_tests/task_local_storage.c b/tools/testing/selftests/bpf/prog_tests/task_local_storage.c
index ea8537c54413..08c589a12bd6 100644
--- a/tools/testing/selftests/bpf/prog_tests/task_local_storage.c
+++ b/tools/testing/selftests/bpf/prog_tests/task_local_storage.c
@@ -5,14 +5,19 @@
 #include <unistd.h>
 #include <sched.h>
 #include <pthread.h>
+#include <sys/mman.h>   /* For mmap and associated flags */
 #include <sys/syscall.h>   /* For SYS_xxx definitions */
 #include <sys/types.h>
 #include <test_progs.h>
+#include <network_helpers.h>
 #include "task_local_storage_helpers.h"
 #include "task_local_storage.skel.h"
 #include "task_local_storage_exit_creds.skel.h"
+#include "task_local_storage__mmap.skel.h"
+#include "task_local_storage__mmap_fail.skel.h"
 #include "task_ls_recursion.skel.h"
 #include "task_storage_nodeadlock.skel.h"
+#include "progs/task_local_storage__mmap.h"
 
 static void test_sys_enter_exit(void)
 {
@@ -40,6 +45,173 @@ static void test_sys_enter_exit(void)
 	task_local_storage__destroy(skel);
 }
 
+static int basic_mmapable_read_write(struct task_local_storage__mmap *skel,
+				     long *mmaped_task_local)
+{
+	int err;
+
+	*mmaped_task_local = 42;
+
+	err = task_local_storage__mmap__attach(skel);
+	if (!ASSERT_OK(err, "skel_attach"))
+		return -1;
+
+	syscall(SYS_gettid);
+	ASSERT_EQ(skel->bss->mmaped_mapval, 42, "mmaped_mapval");
+
+	/* Incr from userspace should be visible when BPF prog reads */
+	*mmaped_task_local = *mmaped_task_local + 1;
+	syscall(SYS_gettid);
+	ASSERT_EQ(skel->bss->mmaped_mapval, 43, "mmaped_mapval_user_incr");
+
+	/* Incr from BPF prog should be visible from userspace */
+	skel->bss->read_and_incr = 1;
+	syscall(SYS_gettid);
+	ASSERT_EQ(skel->bss->mmaped_mapval, 44, "mmaped_mapval_bpf_incr");
+	ASSERT_EQ(skel->bss->mmaped_mapval, *mmaped_task_local, "bpf_incr_eq");
+	skel->bss->read_and_incr = 0;
+
+	return 0;
+}
+
+static void test_sys_enter_mmap(void)
+{
+	struct task_local_storage__mmap *skel;
+	long *task_local, *task_local2, value;
+	int err, task_fd, map_fd;
+
+	task_local = task_local2 = (long *)-1;
+	task_fd = sys_pidfd_open(getpid(), 0);
+	if (!ASSERT_NEQ(task_fd, -1, "sys_pidfd_open"))
+		return;
+
+	skel = task_local_storage__mmap__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) {
+		close(task_fd);
+		return;
+	}
+
+	map_fd = bpf_map__fd(skel->maps.mmapable);
+	task_local = mmap(NULL, sizeof(long), PROT_READ | PROT_WRITE,
+			  MAP_SHARED, map_fd, 0);
+	if (!ASSERT_OK_PTR(task_local, "mmap_task_local_storage"))
+		goto out;
+
+	err = basic_mmapable_read_write(skel, task_local);
+	if (!ASSERT_OK(err, "basic_mmapable_read_write"))
+		goto out;
+
+	err = bpf_map_lookup_elem(map_fd, &task_fd, &value);
+	if (!ASSERT_OK(err, "bpf_map_lookup_elem") ||
+	    !ASSERT_EQ(value, 44, "bpf_map_lookup_elem value"))
+		goto out;
+
+	value = 148;
+	bpf_map_update_elem(map_fd, &task_fd, &value, BPF_EXIST);
+	if (!ASSERT_EQ(READ_ONCE(*task_local), 148, "mmaped_read_after_update"))
+		goto out;
+
+	err = bpf_map_lookup_elem(map_fd, &task_fd, &value);
+	if (!ASSERT_OK(err, "bpf_map_lookup_elem") ||
+	    !ASSERT_EQ(value, 148, "bpf_map_lookup_elem value"))
+		goto out;
+
+	/* The mmapable page is not released by map_delete_elem, but no longer
+	 * linked to local_storage
+	 */
+	err = bpf_map_delete_elem(map_fd, &task_fd);
+	if (!ASSERT_OK(err, "bpf_map_delete_elem") ||
+	    !ASSERT_EQ(READ_ONCE(*task_local), 148, "mmaped_read_after_delete"))
+		goto out;
+
+	err = bpf_map_lookup_elem(map_fd, &task_fd, &value);
+	if (!ASSERT_EQ(err, -ENOENT, "bpf_map_lookup_elem_after_delete"))
+		goto out;
+
+	task_local_storage__mmap__destroy(skel);
+
+	/* The mmapable page is not released when __destroy unloads the map.
+	 * It will stick around until we munmap it
+	 */
+	*task_local = -999;
+
+	/* Although task_local's page is still around, it won't be reused */
+	skel = task_local_storage__mmap__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open_and_load2"))
+		return;
+
+	map_fd = bpf_map__fd(skel->maps.mmapable);
+	err = task_local_storage__mmap__attach(skel);
+	if (!ASSERT_OK(err, "skel_attach2"))
+		goto out;
+
+	skel->bss->read_and_incr = 1;
+	skel->bss->create_flag = BPF_LOCAL_STORAGE_GET_F_CREATE;
+	syscall(SYS_gettid);
+	ASSERT_EQ(skel->bss->mmaped_mapval, 1, "mmaped_mapval2");
+
+	skel->bss->read_and_incr = 0;
+	task_local2 = mmap(NULL, sizeof(long), PROT_READ | PROT_WRITE,
+			   MAP_SHARED, map_fd, 0);
+	if (!ASSERT_OK_PTR(task_local, "mmap_task_local_storage2"))
+		goto out;
+
+	if (!ASSERT_NEQ(task_local, task_local2, "second_mmap_address"))
+		goto out;
+
+	ASSERT_EQ(READ_ONCE(*task_local2), 1, "mmaped_mapval2_bpf_create_incr");
+
+out:
+	close(task_fd);
+	if (task_local > 0)
+		munmap(task_local, sizeof(long));
+	if (task_local2 > 0)
+		munmap(task_local2, sizeof(long));
+	task_local_storage__mmap__destroy(skel);
+}
+
+static void test_sys_enter_mmap_big_mapval(void)
+{
+	struct two_page_struct *task_local, value;
+	struct task_local_storage__mmap *skel;
+	int task_fd, map_fd, err;
+
+	task_local = (struct two_page_struct *)-1;
+	task_fd = sys_pidfd_open(getpid(), 0);
+	if (!ASSERT_NEQ(task_fd, -1, "sys_pidfd_open"))
+		return;
+
+	skel = task_local_storage__mmap__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) {
+		close(task_fd);
+		return;
+	}
+	map_fd = bpf_map__fd(skel->maps.mmapable_two_pages);
+	task_local = mmap(NULL, sizeof(struct two_page_struct),
+			  PROT_READ | PROT_WRITE, MAP_SHARED,
+			  map_fd, 0);
+	if (!ASSERT_OK_PTR(task_local, "mmap_task_local_storage"))
+		goto out;
+
+	skel->bss->use_big_mapval = 1;
+	err = basic_mmapable_read_write(skel, &task_local->val);
+	if (!ASSERT_OK(err, "basic_mmapable_read_write"))
+		goto out;
+
+	task_local->c[4096] = 'z';
+
+	err = bpf_map_lookup_elem(map_fd, &task_fd, &value);
+	if (!ASSERT_OK(err, "bpf_map_lookup_elem") ||
+	    !ASSERT_EQ(value.val, 44, "bpf_map_lookup_elem value"))
+		goto out;
+
+out:
+	close(task_fd);
+	if (task_local > 0)
+		munmap(task_local, sizeof(struct two_page_struct));
+	task_local_storage__mmap__destroy(skel);
+}
+
 static void test_exit_creds(void)
 {
 	struct task_local_storage_exit_creds *skel;
@@ -237,10 +409,15 @@ void test_task_local_storage(void)
 {
 	if (test__start_subtest("sys_enter_exit"))
 		test_sys_enter_exit();
+	if (test__start_subtest("sys_enter_mmap"))
+		test_sys_enter_mmap();
+	if (test__start_subtest("sys_enter_mmap_big_mapval"))
+		test_sys_enter_mmap_big_mapval();
 	if (test__start_subtest("exit_creds"))
 		test_exit_creds();
 	if (test__start_subtest("recursion"))
 		test_recursion();
 	if (test__start_subtest("nodeadlock"))
 		test_nodeadlock();
+	RUN_TESTS(task_local_storage__mmap_fail);
 }
diff --git a/tools/testing/selftests/bpf/progs/task_local_storage__mmap.c b/tools/testing/selftests/bpf/progs/task_local_storage__mmap.c
new file mode 100644
index 000000000000..1c8850c8d189
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/task_local_storage__mmap.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "task_local_storage__mmap.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+	__uint(type, BPF_MAP_TYPE_TASK_STORAGE);
+	__uint(map_flags, BPF_F_NO_PREALLOC | BPF_F_MMAPABLE);
+	__type(key, int);
+	__type(value, long);
+} mmapable SEC(".maps");
+
+struct {
+	__uint(type, BPF_MAP_TYPE_TASK_STORAGE);
+	__uint(map_flags, BPF_F_NO_PREALLOC | BPF_F_MMAPABLE);
+	__type(key, int);
+	__type(value, struct two_page_struct);
+} mmapable_two_pages SEC(".maps");
+
+long mmaped_mapval = 0;
+int read_and_incr = 0;
+int create_flag = 0;
+int use_big_mapval = 0;
+
+SEC("tp_btf/sys_enter")
+int BPF_PROG(on_enter, struct pt_regs *regs, long id)
+{
+	struct two_page_struct *big_mapval;
+	struct task_struct *task;
+	long *ptr;
+
+	task = bpf_get_current_task_btf();
+	if (!task)
+		return 1;
+
+	if (use_big_mapval) {
+		big_mapval = bpf_task_storage_get(&mmapable_two_pages, task, 0,
+						  create_flag);
+		if (!big_mapval)
+			return 2;
+		ptr = &big_mapval->val;
+	} else {
+		ptr = bpf_task_storage_get(&mmapable, task, 0, create_flag);
+	}
+
+	if (!ptr)
+		return 3;
+
+	if (read_and_incr)
+		*ptr = *ptr + 1;
+
+	mmaped_mapval = *ptr;
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/task_local_storage__mmap.h b/tools/testing/selftests/bpf/progs/task_local_storage__mmap.h
new file mode 100644
index 000000000000..f4a3264142c2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/task_local_storage__mmap.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
+
+struct two_page_struct {
+	long val;
+	char c[4097];
+};
diff --git a/tools/testing/selftests/bpf/progs/task_local_storage__mmap_fail.c b/tools/testing/selftests/bpf/progs/task_local_storage__mmap_fail.c
new file mode 100644
index 000000000000..f32c5bfe370a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/task_local_storage__mmap_fail.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+	__uint(type, BPF_MAP_TYPE_TASK_STORAGE);
+	__uint(map_flags, BPF_F_NO_PREALLOC | BPF_F_MMAPABLE);
+	__type(key, int);
+	__type(value, long);
+} mmapable SEC(".maps");
+
+__failure __msg("invalid access to map value, value_size=8 off=8 size=8")
+SEC("tp_btf/sys_enter")
+long BPF_PROG(fail_read_past_mapval_end, struct pt_regs *regs, long id)
+{
+	struct task_struct *task;
+	long *ptr;
+	long res;
+
+	task = bpf_get_current_task_btf();
+	if (!task)
+		return 1;
+
+	ptr = bpf_task_storage_get(&mmapable, task, 0, 0);
+	if (!ptr)
+		return 3;
+	/* Although mmapable mapval is given an entire page, verifier shouldn't
+	 * allow r/w past end of 'long' type
+	 */
+	res = *(ptr + 1);
+
+	return res;
+}
