Use BPF to block socket creation when restricted
Adapt BPF's inet socket creation rule to not only check INTERNET
permission but also to ensure the uid is on the allowlist for
restricted networking (has RESTRICTED_MATCH flag).
Also includes squashed change:
Author: Tommy Webb <tommy@calyxinstitute.org>
Date: Thu Sep 7 03:59:21 2023 -0400
Deny socket creation for transport-blocked apps
Prevent apps whose network access is blocked based on transport
policies from creating sockets, too. Update the logic to match AOSP's
"funky bit-wise arithmetic" from the latest Connectivity mainline.
Test: Manual: Turn on Private DNS. Install Terminal Emulator. Connect
to Wi-Fi (no VPN). Set Terminal Emulator's toggles to disable Wi-Fi.
Run: `ping duckduckgo.com`. Should receive "unknown host" error, NOT
"Network is unreachable". Same "unknown host" error should occur when
testing with overall network access turned off for Terminal Emulator,
with and without its Wi-Fi access also turned off.
Issue: calyxos#581
Change-Id: I995e9929f6f8c1ae0613e05e0cade55a76c35902
Co-authored-by: Oliver Scott <olivercscott@gmail.com>
Change-Id: I912a4a2ee78a29ca8b7d8ff85e5ad7cf617c31a5
Signed-off-by: Dmitrii <bankersenator@gmail.com>
Signed-off-by: Jis G Jacob <studiokeys@blissroms.org>
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index f223dd1..d8ae7bb 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -369,6 +369,7 @@
return *config;
}
+
static __always_inline inline bool ingress_should_discard(struct __sk_buff* skb,
const struct kver_uint kver) {
// Require 4.19, since earlier kernels don't have bpf_skb_load_bytes_relative() which
@@ -398,6 +399,21 @@
return true; // disallowed interface
}
+static __always_inline inline int bpf_owner_firewall_match(uint32_t uid) {
+ if (is_system_uid(uid)) return PASS;
+
+ const BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
+ const UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
+ const uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
+
+ if (enabledRules & (FIREWALL_DROP_IF_SET | FIREWALL_DROP_IF_UNSET)
+ & (uidRules ^ FIREWALL_DROP_IF_UNSET)) {
+ return DROP;
+ }
+
+ return PASS;
+}
+
static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid,
const struct egress_bool egress,
const struct kver_uint kver) {
@@ -637,15 +653,14 @@
return BPF_NOMATCH;
}
-static __always_inline inline uint8_t get_app_permissions() {
- uint64_t gid_uid = bpf_get_current_uid_gid();
+static __always_inline inline uint8_t get_app_permissions(uint32_t uid) {
/*
* A given app is guaranteed to have the same app ID in all the profiles in
* which it is installed, and install permission is granted to app for all
* user at install time so we only check the appId part of a request uid at
* run time. See UserHandle#isSameApp for detail.
*/
- uint32_t appId = (gid_uid & 0xffffffff) % AID_USER_OFFSET; // == PER_USER_RANGE == 100000
+ uint32_t appId = uid % AID_USER_OFFSET; // == PER_USER_RANGE == 100000
uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
// if UID not in map, then default to just INTERNET permission.
return permissions ? *permissions : BPF_PERMISSION_INTERNET;
@@ -654,8 +669,13 @@
DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
KVER_4_14)
(struct bpf_sock* sk) {
+ uint64_t uid = bpf_get_current_uid_gid() & 0xffffffff;
// A return value of 1 means allow, everything else means deny.
- return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? 1 : 0;
+ if (get_app_permissions(uid) & BPF_PERMISSION_INTERNET) {
+ return bpf_owner_firewall_match(uid) == PASS ? 1 : 0;
+ } else {
+ return 0;
+ }
}
LICENSE("Apache 2.0");
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 64ed633..53fa6f3 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -117,13 +117,13 @@
// 'static' - otherwise these constants end up in .rodata in the resulting .o post compilation
static const int COOKIE_UID_MAP_SIZE = 10000;
-static const int UID_COUNTERSET_MAP_SIZE = 4000;
+static const int UID_COUNTERSET_MAP_SIZE = 40000;
static const int APP_STATS_MAP_SIZE = 10000;
static const int STATS_MAP_SIZE = 5000;
static const int IFACE_INDEX_NAME_MAP_SIZE = 1000;
static const int IFACE_STATS_MAP_SIZE = 1000;
static const int CONFIGURATION_MAP_SIZE = 2;
-static const int UID_OWNER_MAP_SIZE = 4000;
+static const int UID_OWNER_MAP_SIZE = 40000;
static const int INGRESS_DISCARD_MAP_SIZE = 100;
static const int PACKET_TRACE_BUF_SIZE = 32 * 1024;
static const int DATA_SAVER_ENABLED_MAP_SIZE = 1;
@@ -248,6 +248,9 @@
#define DROP_IF_UNSET (DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH \
| LOW_POWER_STANDBY_MATCH | BACKGROUND_MATCH)
+#define FIREWALL_DROP_IF_SET (OEM_DENY_1_MATCH)
+#define FIREWALL_DROP_IF_UNSET (RESTRICTED_MATCH)
+
// Warning: funky bit-wise arithmetic: in parallel, for all DROP_IF_SET/UNSET rules
// check whether the rules are globally enabled, and if so whether the rules are
// set/unset for the specific uid. DROP if that is the case for ANY of the rules.