Skip to content
Open
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
90 changes: 46 additions & 44 deletions bpf/progs/netd.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ DEFINE_BPF_MAP_NO_NETD_API(local_net_note_op_enabled_map, ARRAY, uint32_t, bool,
// used.
DEFINE_BPF_MAP_NO_NETD_API(local_net_cache_generation_id_map, ARRAY, uint32_t, uint64_t, 1, 25Q2)

// A ring buffer on which blocked SO_BINDTODEVICE events are pushed.
DEFINE_BPF_RINGBUF_EXT(sk_bind_to_device_event_ringbuf, SkBindToDeviceEvent, 8 * 512,
AID_ROOT, AID_SYSTEM, 0060, "net_shared", DEFAULT_BPF_PIN_SUBDIR,
26Q2, MAXAPI);

// A ring buffer on which loopback access events are pushed.
DEFINE_BPF_RINGBUF_EXT(loopback_access_ringbuf, LoopbackAccessEvent, 16 * 512,
AID_ROOT, AID_SYSTEM, 0060, "net_shared", DEFAULT_BPF_PIN_SUBDIR,
Expand Down Expand Up @@ -1506,57 +1511,54 @@ function int inet_setsockopt(struct bpf_sockopt *ctx,
UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
uint32_t uidRule = uidEntry ? uidEntry->rule : 0;

if (ctx->level == SOL_SOCKET
&& ctx->optname == SO_BINDTODEVICE
&& !is_system_uid(uid)
&& !(uidRule & APP_STRICT_LEAK_BLOCKING_DISABLED_MATCH)) {
SkBindToDeviceEvent *event = bpf_sk_bind_to_device_event_ringbuf_reserve();
if (event != NULL) {
event->uid = uid;
// Refer to is_netd() for explanation of shift.
event->pid = bpf_get_current_pid_tgid() >> 32;
bpf_sk_bind_to_device_event_ringbuf_submit(event);
}
return SETSOCKOPT_EPERM;
}

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

{
// Prevent SO_BINDTODEVICE from being triggered by a UID that is under a lockdown VPN as
// this can leak unicast traffic. Can only do this for regular apps as some core system and
// system apps rely on this being allowed.
// TODO: Review IP_UNICAST_IF and IP_PKTINFO.
// TODO: Review PermissionMonitor#hasRestrictedNetworkPermission to see if this covers all
// of the system uids that need to SO_BINDTODEVICE. These uids do not have
// LOCKDOWN_VPN_MATCH.
if ((uidRule & LOCKDOWN_VPN_REGULAR_APP_MATCH)
&& ctx->level == SOL_SOCKET
&& ctx->optname == SO_BINDTODEVICE) {
return SETSOCKOPT_EPERM;
}
// Prevent 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.
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;
}

{
// Prevent 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.
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_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;
}
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;
Expand Down
10 changes: 9 additions & 1 deletion bpf/progs/netd.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ ASSERT_STRING_EQUAL(XT_BPF_DENYLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilte
#define LOCAL_NET_NOTE_OP_RINGBUF_PATH BPF_NETD_PATH "map_netd_local_net_note_op_ringbuf"
#define LOCAL_NET_NOTE_OP_CACHE_MAP_PATH BPF_NETD_PATH "map_netd_local_net_note_op_cache_map"
#define LOCAL_NET_NOTE_OP_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_local_net_note_op_enabled_map"
#define SK_BIND_TO_DEVICE_EVENT_RINGBUF_PATH BPF_NETD_PATH "map_netd_sk_bind_to_device_event_ringbuf"
#define LOCAL_NET_CACHE_GENERATION_ID_MAP_PATH \
BPF_NETD_PATH "map_netd_local_net_cache_generation_id_map"
#define LOOPBACK_ACCESS_RINGBUF_NETD_PATH BPF_NETD_PATH "map_netd_loopback_access_ringbuf"
Expand Down Expand Up @@ -270,7 +271,7 @@ enum UidOwnerMatchType : uint32_t {
OEM_DENY_3_MATCH = (1 << 11),
BACKGROUND_MATCH = (1 << 12),
PENALTY_BOX_ADMIN_MATCH = (1 << 13),
LOCKDOWN_VPN_REGULAR_APP_MATCH = (1 << 30),
APP_STRICT_LEAK_BLOCKING_DISABLED_MATCH = (1 << 30),
};
// LINT.ThenChange(../framework/src/android/net/BpfNetMapsConstants.java)

Expand Down Expand Up @@ -361,6 +362,13 @@ typedef struct {
} LocalNetNoteOp;
STRUCT_SIZE(LocalNetNoteOp, 4 + 4); // 8

// uid and pid of process that had their attempt to use SO_BINDTODEVICE blocked.
typedef struct {
uint32_t uid;
uint32_t pid;
} SkBindToDeviceEvent;
STRUCT_SIZE(SkBindToDeviceEvent, 4 + 4); // 8

// IP packet data from an __sk_buff
typedef struct {
struct in6_addr saddr; // Stores v6 or v4-mapped-v6
Expand Down
5 changes: 3 additions & 2 deletions framework/src/android/net/BpfNetMapsConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private BpfNetMapsConstants() {}
public static final long OEM_DENY_3_MATCH = (1 << 11);
public static final long BACKGROUND_MATCH = (1 << 12);
public static final long PENALTY_BOX_ADMIN_MATCH = (1 << 13);
public static final long LOCKDOWN_VPN_REGULAR_APP_MATCH = (1 << 30);
public static final long APP_STRICT_LEAK_BLOCKING_DISABLED_MATCH = (1 << 30);

public static final List<Pair<Long, String>> MATCH_LIST = Arrays.asList(
Pair.create(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"),
Expand All @@ -126,7 +126,8 @@ private BpfNetMapsConstants() {}
Pair.create(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH"),
Pair.create(BACKGROUND_MATCH, "BACKGROUND_MATCH"),
Pair.create(PENALTY_BOX_ADMIN_MATCH, "PENALTY_BOX_ADMIN_MATCH"),
Pair.create(LOCKDOWN_VPN_REGULAR_APP_MATCH, "LOCKDOWN_VPN_REGULAR_APP_MATCH")
Pair.create(APP_STRICT_LEAK_BLOCKING_DISABLED_MATCH,
"APP_STRICT_LEAK_BLOCKING_DISABLED_MATCH")
);

/**
Expand Down
1 change: 1 addition & 0 deletions service/Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ cc_library_shared {
"jni/com_android_server_connectivity_BpfEventPoller.cpp",
"jni/com_android_server_connectivity_ClatCoordinator.cpp",
"jni/com_android_server_connectivity_LocalNetEventListener.cpp",
"jni/com_android_server_connectivity_SkBindToDeviceEventListener.cpp",
"jni/onload.cpp",
],
header_libs: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Based on Connectivity/service/jni/com_android_server_connectivity_LocalNetEventListener.cpp

#define LOG_TAG "SkBindToDeviceEventListenerNative"

#include <android-base/unique_fd.h>
#include <android/file_descriptor_jni.h>
#include <jni.h>
#include <nativehelper/JNIPlatformHelp.h>
#include <nativehelper/jni_macros.h>
#include <nativehelper/scoped_local_ref.h>
#include <utils/misc.h>

#include "bpf/BpfUtils.h"
#include "libeventpolling/SkBindToDeviceEventHandler.h"

namespace android {

using android::net::eventpolling::SkBindToDeviceEventHandler;

static jobject nativeGetSkBindToDeviceEventRingbufFd(JNIEnv *env, jclass clazz) {
android::base::unique_fd fd = SkBindToDeviceEventHandler::GetNewRingbufFd();
if (!fd.ok()) {
ALOGE("Failed to get sk_bind_to_device_event_ringbuf fd");
return nullptr;
}
return jniCreateFileDescriptor(env, fd.release());
}

static jintArray nativeConsumeAllSkBindToDeviceEvents(JNIEnv *env,
jclass clazz) {
std::vector<uint32_t> uids_pids = SkBindToDeviceEventHandler::ConsumeAll();
if (uids_pids.empty()) {
return env->NewIntArray(0);
}

ScopedLocalRef<jintArray> result(env, env->NewIntArray(uids_pids.size()));
if (!result.get()) {
ALOGE("Failed to allocate jintArray");
return nullptr;
}
env->SetIntArrayRegion(result.get(), 0, uids_pids.size(),
reinterpret_cast<const jint *>(uids_pids.data()));
return result.release();
}

static const JNINativeMethod gMethods[] = {
MAKE_JNI_NATIVE_METHOD("nativeGetSkBindToDeviceEventRingbufFd",
"()Ljava/io/FileDescriptor;",
nativeGetSkBindToDeviceEventRingbufFd),
MAKE_JNI_NATIVE_METHOD_AUTOSIG("nativeConsumeAllSkBindToDeviceEvents",
nativeConsumeAllSkBindToDeviceEvents),
};

int register_com_android_server_connectivity_SkBindToDeviceEventListener(
JNIEnv *env) {
return jniRegisterNativeMethods(env,
"android/net/connectivity/com/android/"
"server/connectivity/SkBindToDeviceEventListener",
gMethods, NELEM(gMethods));
}
} // namespace android
5 changes: 5 additions & 0 deletions service/jni/onload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ namespace android {
int register_com_android_server_connectivity_BpfEventPoller(JNIEnv *env);
int register_com_android_server_connectivity_ClatCoordinator(JNIEnv *env);
int register_com_android_server_connectivity_LocalNetEventListener(JNIEnv *env);
int register_com_android_server_connectivity_SkBindToDeviceEventListener(JNIEnv *env);
int register_android_server_net_NetworkStatsFactory(JNIEnv *env);
int register_android_server_net_NetworkStatsService(JNIEnv *env);
int register_com_android_net_module_util_ServiceConnectivityJni(JNIEnv *env,
Expand All @@ -49,6 +50,10 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
return JNI_ERR;
}

if (register_com_android_server_connectivity_SkBindToDeviceEventListener(env) < 0) {
return JNI_ERR;
}

if (register_android_server_net_NetworkStatsFactory(env) < 0) {
return JNI_ERR;
}
Expand Down
1 change: 1 addition & 0 deletions service/native/libs/libeventpolling/Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ cc_library_static {
srcs: [
"LocalNetEventHandler.cpp",
"LoopbackEventHandler.cpp",
"SkBindToDeviceEventHandler.cpp",
],
defaults: ["libstatslog_defaults"],
export_include_dirs: ["include"],
Expand Down
48 changes: 48 additions & 0 deletions service/native/libs/libeventpolling/SkBindToDeviceEventHandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Based on Connectivity/service/native/libs/libeventpolling/LocalNetEventHandler.cpp

#define LOG_TAG "SkBindToDeviceEventHandler"

#include "libeventpolling/SkBindToDeviceEventHandler.h"

#include <memory>
#include <vector>

#include <bpf/BpfRingbuf.h>
#include <bpf/BpfUtils.h>
#include <log/log.h>
#include <statslog_connectivity_sdk30.h>

#include "netd.h"

namespace android::net::eventpolling {

using bpf::BpfRingbuf;
using bpf::RingbufEventPoller;

// static
SkBindToDeviceEventHandler::SkBindToDeviceEventRingbuf *SkBindToDeviceEventHandler::GetRingbuf() {
static SkBindToDeviceEventRingbuf *const sRingbuf =
[]() -> SkBindToDeviceEventRingbuf * {
auto rb = std::make_unique<SkBindToDeviceEventRingbuf>(
SK_BIND_TO_DEVICE_EVENT_RINGBUF_PATH);
return rb.release();
}();
return sRingbuf;
}

// static
std::vector<uint32_t> SkBindToDeviceEventHandler::ConsumeAll() {
std::vector<uint32_t> uids_pids;
base::Result<int> ret =
GetRingbuf()->ConsumeAll([&](const SkBindToDeviceEvent &event) {
uids_pids.push_back(event.uid);
uids_pids.push_back(event.pid);
});
if (!ret.ok()) {
ALOGW("Failed to poll ringbuf: %s", ret.error().message().c_str());
return {};
}
return uids_pids;
}

} // namespace android::net::eventpolling
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Based on Connectivity/service/native/libs/libeventpolling/include/libeventpolling/LocalNetEventHandler.h

#pragma once

#include <vector>

#include <android-base/unique_fd.h>
#include <bpf/RingbufEventPoller.h>

#include "netd.h"

namespace android::net::eventpolling {

class SkBindToDeviceEventHandler {
public:
SkBindToDeviceEventHandler() = delete;
SkBindToDeviceEventHandler(const SkBindToDeviceEventHandler &) = delete;
SkBindToDeviceEventHandler &operator=(const SkBindToDeviceEventHandler &) = delete;

static android::base::unique_fd GetNewRingbufFd() {
return GetRingbuf()->GetDuplicateFd();
}

// Consumes all available events in the ring buffer. Returns a list of
// alternating UIDs / PIDs, where each UID / PID pair represents a single
// access event.
static std::vector<uint32_t> ConsumeAll();

private:
class SkBindToDeviceEventRingbuf : public bpf::BpfRingbuf<SkBindToDeviceEvent> {
public:
SkBindToDeviceEventRingbuf(const char *path)
: BpfRingbuf<SkBindToDeviceEvent>(path) {}

android::base::unique_fd GetDuplicateFd() {
return android::base::unique_fd(
fcntl(mRingFd.get(), F_DUPFD_CLOEXEC, 0));
}
};

static SkBindToDeviceEventRingbuf *GetRingbuf();
};

} // namespace android::net::eventpolling
Loading