diff --git a/private/app_neverallows.te b/private/app_neverallows.te
index c1f9a2b01aa639b7a9e9fde858d49b8c34e1ce0a..46b49c274d426da392f0eaecb9e89be1241499af 100644
--- a/private/app_neverallows.te
+++ b/private/app_neverallows.te
@@ -112,8 +112,35 @@ neverallow { all_untrusted_apps -mediaprovider } {
 # No untrusted component should be touching /dev/fuse
 neverallow all_untrusted_apps fuse_device:chr_file *;
 
-# Do not allow untrusted apps to directly open tun_device
-neverallow all_untrusted_apps tun_device:chr_file open;
+# Do not allow untrusted apps to directly open or
+# issue ioctls to the tun_device
+neverallow all_untrusted_apps tun_device:chr_file { open ioctl };
+# Additionally, assert that the following ioctls are never reachable.
+# This should already be blocked by the neverallow rule above, but this
+# is added for robustness, and to prove equivalence to the kernel patch at
+# https://android.googlesource.com/kernel/common/+/11cee2be0c2062ba88f04eb51196506f870a3b5d%5E%21
+neverallowxperm all_untrusted_apps tun_device:chr_file ioctl {
+  SIOCGIFHWADDR
+  SIOCSIFHWADDR
+  TUNATTACHFILTER
+  TUNDETACHFILTER
+  TUNGETFEATURES
+  TUNGETFILTER
+  TUNGETSNDBUF
+  TUNGETVNETHDRSZ
+  TUNSETDEBUG
+  TUNSETGROUP
+  TUNSETIFF
+  TUNSETLINK
+  TUNSETNOCSUM
+  TUNSETOFFLOAD
+  TUNSETOWNER
+  TUNSETPERSIST
+  TUNSETQUEUE
+  TUNSETSNDBUF
+  TUNSETTXFILTER
+  TUNSETVNETHDRSZ
+};
 
 # Only allow appending to /data/anr/traces.txt (b/27853304, b/18340553)
 neverallow all_untrusted_apps anr_data_file:file ~{ open append };
diff --git a/private/isolated_app.te b/private/isolated_app.te
index a17f22a4c786d99de79de0c576c1d69193210bc2..1b56c5cf8c681867a0dbec3a5870417dbb5b2871 100644
--- a/private/isolated_app.te
+++ b/private/isolated_app.te
@@ -57,9 +57,6 @@ unix_socket_connect(isolated_app, traced_producer, traced)
 ##### Neverallow
 #####
 
-# Do not allow isolated_app to directly open tun_device
-neverallow isolated_app tun_device:chr_file open;
-
 # Isolated apps should not directly open app data files themselves.
 neverallow isolated_app { app_data_file privapp_data_file }:file open;
 
diff --git a/private/system_server.te b/private/system_server.te
index 506378e463e1e5caca502388020df84eb313778f..a96b82be61a64da5fa30879f79d239aac5be3bd4 100644
--- a/private/system_server.te
+++ b/private/system_server.te
@@ -339,6 +339,7 @@ allow system_server audio_device:chr_file rw_file_perms;
 
 # tun device used for 3rd party vpn apps
 allow system_server tun_device:chr_file rw_file_perms;
+allowxperm system_server tun_device:chr_file ioctl { TUNGETIFF TUNSETIFF };
 
 # Manage system data files.
 allow system_server system_data_file:dir create_dir_perms;
diff --git a/public/app.te b/public/app.te
index 7f0d5548ec3bb68e474adf52621c4a25acef7ce4..549930291c3323ce128cc1e137cfdc7d4bed3295 100644
--- a/public/app.te
+++ b/public/app.te
@@ -334,7 +334,7 @@ allow appdomain runas_exec:file getattr;
 
 # Apps receive an open tun fd from the framework for
 # device traffic. Do not allow untrusted app to directly open tun_device
-allow { appdomain -isolated_app -ephemeral_app } tun_device:chr_file { read write getattr ioctl append };
+allow { appdomain -isolated_app -ephemeral_app } tun_device:chr_file { read write getattr append };
 
 # Connect to adbd and use a socket transferred from it.
 # This is used for e.g. adb backup/restore.
diff --git a/public/domain.te b/public/domain.te
index 0a838a3d4f25b89abd3591b7dce7f881c26e50f3..0244b7a450149cd80169e0757b3f68b77d6de251 100644
--- a/public/domain.te
+++ b/public/domain.te
@@ -300,6 +300,10 @@ allowxperm domain devpts:chr_file ioctl unpriv_tty_ioctls;
 # named pipes, and named sockets). We start off with a safe set.
 allowxperm domain { file_type fs_type domain dev_type }:{ dir notdevfile_class_set blk_file } ioctl { FIOCLEX FIONCLEX };
 
+# If a domain has ioctl access to tun_device, it must clearly enumerate the
+# ioctls used. Safe defaults are listed below.
+allowxperm domain tun_device:chr_file ioctl { FIOCLEX FIONCLEX };
+
 # Allow a process to make a determination whether a file descriptor
 # for a plain file or pipe (fifo_file) is a tty. Note that granting
 # this whitelist to domain does not grant the ioctl permission to