Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set allocated size for permission mapping to maximum number of uid po… #21

Draft
wants to merge 13 commits into
base: 15
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.android.networkstack.tethering;

import static android.content.Context.TELEPHONY_SERVICE;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
Expand All @@ -34,7 +33,6 @@
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;

import androidx.annotation.NonNull;
Expand Down Expand Up @@ -79,7 +77,7 @@ public class TetheringConfiguration {
"192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
};

private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
private static final String[] DEFAULT_IPV4_DNS = {"1.0.0.1", "1.1.1.1"};

@VisibleForTesting
public static final int TETHER_USB_RNDIS_FUNCTION = 0;
Expand Down Expand Up @@ -505,10 +503,7 @@ private static String makeString(String[] strings) {

/** Check whether dun is required. */
public static boolean checkDunRequired(Context ctx) {
final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
// TelephonyManager would uses the active data subscription, which should be the one used
// by tethering.
return (tm != null) ? tm.isTetheringApnRequired() : false;
return false;
}

public int getOffloadPollInterval() {
Expand Down
106 changes: 88 additions & 18 deletions bpf_progs/netd.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ static const int BPF_MATCH = 1;
static const bool TRACE_ON = true;
static const bool TRACE_OFF = false;

// Used for setsockopt/lockdown_vpn_multicast.
static const int SETSOCKOPT_EPERM = 0;
static const int SETSOCKOPT_ALLOWED = 1;

// offsetof(struct iphdr, ihl) -- but that's a bitfield
#define IPPROTO_IHL_OFF 0

Expand Down Expand Up @@ -406,13 +410,26 @@ static __always_inline inline bool ingress_should_discard(struct __sk_buff* skb,
return true; // disallowed interface
}

static __always_inline inline bool is_multicast(struct __sk_buff* skb,
const struct kver_uint kver) {
uint8_t addr_first_octet;
if (skb->protocol == htons(ETH_P_IP)) {
__u32 daddr4;
(void) bpf_skb_load_bytes_net(skb, IP4_OFFSET(daddr), &daddr4, sizeof(daddr4), kver);
addr_first_octet = (ntohl(daddr4) >> 24) & 0xFF;
if (addr_first_octet >= 224 && addr_first_octet <= 239) return true;
} else if (skb->protocol == htons(ETH_P_IPV6)) {
__u32 daddr6[4];
(void) bpf_skb_load_bytes_net(skb, IP6_OFFSET(daddr), &daddr6, sizeof(daddr6), kver);
addr_first_octet = (ntohl(daddr6[0]) >> 24) & 0xFF;
if (addr_first_octet == 0xFF) return true;
}
return false;
}

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) {
if (is_system_uid(uid)) return PASS;

if (skip_owner_match(skb, egress, kver)) return PASS;

BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);

// BACKGROUND match does not apply to loopback traffic
Expand All @@ -422,6 +439,14 @@ static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_
uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;

if ((uidRules & LOCKDOWN_VPN_MATCH) && is_multicast(skb, kver)) {
return DROP;
}

if (is_system_uid(uid)) return PASS;

if (skip_owner_match(skb, egress, kver)) return PASS;

if (isBlockedByUidRules(enabledRules, uidRules)) return DROP;

if (!egress.egress && skb->ifindex != 1) {
Expand Down Expand Up @@ -466,6 +491,8 @@ static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb,
// packets to an unconnected udp socket.
// But it can also happen for egress from a timewait socket.
// Let's treat such cases as 'root' which is_system_uid()
// TODO: Verify that this can never occur for multicast traffic. Have done manual testing, but
// need to read the kernel networking code to see if it is possible.
if (sock_uid == 65534) sock_uid = 0;

uint64_t cookie = bpf_get_socket_cookie(skb); // 0 iff !skb->sk
Expand Down Expand Up @@ -662,14 +689,8 @@ DEFINE_XTBPF_PROG("skfilter/denylist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_den

static __always_inline inline uint8_t get_app_permissions() {
uint64_t gid_uid = bpf_get_current_uid_gid();
/*
* 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
uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
uint32_t uid = (gid_uid & 0xffffffff);
uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&uid);
// if UID not in map, then default to just INTERNET permission.
return permissions ? *permissions : BPF_PERMISSION_INTERNET;
}
Expand Down Expand Up @@ -738,20 +759,69 @@ DEFINE_NETD_V_BPF_PROG_KVER("sendmsg6/udp6_sendmsg", AID_ROOT, AID_ROOT, udp6_se
return check_localhost(ctx);
}

DEFINE_NETD_V_BPF_PROG_KVER("getsockopt/prog", AID_ROOT, AID_ROOT, getsockopt_prog, KVER_5_4)
DEFINE_NETD_V_BPF_PROG_KVER("getsockopt/prog", AID_ROOT, AID_ROOT, getsockopt_prog, KVER_5_8)
(struct bpf_sockopt *ctx) {
// Tell kernel to return 'original' kernel reply (instead of the bpf modified buffer)
// This is important if the answer is larger than PAGE_SIZE (max size this bpf hook can provide)
ctx->optlen = 0;
return 1; // ALLOW
}

DEFINE_NETD_V_BPF_PROG_KVER("setsockopt/prog", AID_ROOT, AID_ROOT, setsockopt_prog, KVER_5_4)
(struct bpf_sockopt *ctx) {
// Tell kernel to use/process original buffer provided by userspace.
// This is important if it is larger than PAGE_SIZE (max size this bpf hook can handle).
// Upstream uses 0 for attach_flags, so can only attach one program per attach type per cgroup.
// See include/uapi/linux/bpf.h. If this program becomes too difficult to understand once upstream
// are actually doing something with it, we can look into using BPF_F_ALLOW_MULTI or just using
// a separate function for each program.
//
// This program prevents kernel-generated multicast traffic (IGMP, MLD) from being triggered by a
// UID that is under a lockdown VPN. A known leak that still exists is when a UID joins a multicast
// group prior to being under a lockdown VPN and then becomes under a lockdown VPN. In this case the
// IGMP/MLD will be generated when the kernel destroys the thread. This is considered very low
// severity.
DEFINE_NETD_BPF_PROG_KVER("setsockopt/prog", AID_ROOT, AID_ROOT, setsockopt_prog, KVER_5_8)
(struct bpf_sockopt* ctx) {
// Force use of original userspace value in setsockopt call, otherwise will have problems with
// values > PAGE_SIZE.
// https://github.com/torvalds/linux/commit/d8fe449a9c51a37d844ab607e14e2f5c657d3cf2
ctx->optlen = 0;
return 1; // ALLOW

uint64_t gid_uid = bpf_get_current_uid_gid();
uint32_t uid = (gid_uid & 0xFFFFFFFF);

UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
uint32_t uidRule = uidEntry ? uidEntry->rule : 0;

if (!(uidRule & LOCKDOWN_VPN_MATCH)) {
return SETSOCKOPT_ALLOWED;
}

if (ctx->level == IPPROTO_IP
&& (ctx->optname == IP_ADD_MEMBERSHIP
|| ctx->optname == IP_ADD_SOURCE_MEMBERSHIP
|| ctx->optname == IP_DROP_MEMBERSHIP
|| ctx->optname == IP_DROP_SOURCE_MEMBERSHIP
|| ctx->optname == IP_BLOCK_SOURCE
|| ctx->optname == IP_UNBLOCK_SOURCE
|| ctx->optname == IP_MSFILTER)) {
return SETSOCKOPT_EPERM;
}

if (ctx->level == IPPROTO_IPV6
&& (ctx->optname == IPV6_ADD_MEMBERSHIP /** IPV6_JOIN_GROUP **/
|| ctx->optname == IPV6_DROP_MEMBERSHIP /** IPV6_LEAVE_GROUP **/)) {
return SETSOCKOPT_EPERM;
}

if ((ctx->level == IPPROTO_IP || ctx->level == IPPROTO_IPV6)
&& (ctx->optname == MCAST_JOIN_GROUP
|| ctx->optname == MCAST_LEAVE_GROUP
|| ctx->optname == MCAST_BLOCK_SOURCE
|| ctx->optname == MCAST_UNBLOCK_SOURCE
|| ctx->optname == MCAST_JOIN_SOURCE_GROUP
|| ctx->optname == MCAST_LEAVE_SOURCE_GROUP)) {
return SETSOCKOPT_EPERM;
}

return SETSOCKOPT_ALLOWED;
}

LICENSE("Apache 2.0");
Expand Down
5 changes: 3 additions & 2 deletions bpf_progs/netd.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,13 @@ STRUCT_SIZE(PacketTrace, 8+4+4 + 4+4 + 2+2 + 1+1+1+1);

// '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 = 3300000;
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 = 3300000;
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;
Expand Down Expand Up @@ -165,6 +165,7 @@ ASSERT_STRING_EQUAL(XT_BPF_DENYLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilte
#define CGROUP_UDP6_SENDMSG_PROG_PATH BPF_NETD_PATH "prog_netd_sendmsg6_udp6_sendmsg"
#define CGROUP_GETSOCKOPT_PROG_PATH BPF_NETD_PATH "prog_netd_getsockopt_prog"
#define CGROUP_SETSOCKOPT_PROG_PATH BPF_NETD_PATH "prog_netd_setsockopt_prog"
#define CGROUP_SOCKET_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"

#define TC_BPF_INGRESS_ACCOUNT_PROG_NAME "prog_netd_schedact_ingress_account"
#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_NETD_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
Expand Down
41 changes: 36 additions & 5 deletions framework-t/src/android/net/nsd/NsdManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import android.net.ConnectivityThread;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.nsd.IOffloadEngine;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
Expand Down Expand Up @@ -724,11 +725,41 @@ public NsdManager(Context context, INsdManager service) {
// Instead of launching separate threads to handle tasks from the various instances.
mHandler = new ServiceHandler(ConnectivityThread.getInstanceLooper());

try {
mService = service.connect(new NsdCallbackImpl(mHandler), CompatChanges.isChangeEnabled(
ENABLE_PLATFORM_MDNS_BACKEND));
} catch (RemoteException e) {
throw new RuntimeException("Failed to connect to NsdService");
if (android.content.pm.SpecialRuntimePermAppUtils.isInternetCompatEnabled()) {
// INsdManager#connect() enforces INTERNET permission
mService = new INsdServiceConnector() {
final NsdCallbackImpl callback = new NsdCallbackImpl(mHandler);

@Override public void registerService(int listenerKey, AdvertisingRequest advertisingRequest) {
callback.onRegisterServiceFailed(listenerKey, FAILURE_INTERNAL_ERROR);
}
@Override public void unregisterService(int listenerKey) {
callback.onUnregisterServiceFailed(listenerKey, FAILURE_INTERNAL_ERROR);
}
@Override public void discoverServices(int listenerKey, DiscoveryRequest discoveryRequest) {
callback.onDiscoverServicesFailed(listenerKey, FAILURE_INTERNAL_ERROR);
}
@Override public void stopDiscovery(int listenerKey) {
callback.onStopDiscoveryFailed(listenerKey, FAILURE_INTERNAL_ERROR);
}
@Override public void resolveService(int listenerKey, NsdServiceInfo serviceInfo) {
callback.onResolveServiceFailed(listenerKey, FAILURE_INTERNAL_ERROR);
}
@Override public void startDaemon() {}
@Override public void stopResolution(int listenerKey) {}
@Override public void registerServiceInfoCallback(int listenerKey, NsdServiceInfo serviceInfo) {}
@Override public void unregisterServiceInfoCallback(int listenerKey) {}
@Override public void registerOffloadEngine(String ifaceName, IOffloadEngine cb, long offloadCapabilities, long offloadType) {}
@Override public void unregisterOffloadEngine(IOffloadEngine cb) {}
@Override public android.os.IBinder asBinder() { return null; }
};
} else {
try {
mService = service.connect(new NsdCallbackImpl(mHandler), CompatChanges.isChangeEnabled(
ENABLE_PLATFORM_MDNS_BACKEND));
} catch (RemoteException e) {
throw new RuntimeException("Failed to connect to NsdService");
}
}

// Only proactively start the daemon if the target SDK < S AND platform < V, For target
Expand Down
Loading