diff options
Diffstat (limited to 'service/src')
5 files changed, 327 insertions, 6 deletions
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index b4cc41a94d..d79bdb8320 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -2238,6 +2238,13 @@ public class ConnectivityService extends IConnectivityManager.Stub callingAttributionTag); } + private void redactUnderlyingNetworksForCapabilities(NetworkCapabilities nc, int pid, int uid) { + if (nc.getUnderlyingNetworks() != null + && !checkNetworkFactoryOrSettingsPermission(pid, uid)) { + nc.setUnderlyingNetworks(null); + } + } + @VisibleForTesting NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions( NetworkCapabilities nc, int callerPid, int callerUid) { @@ -2250,8 +2257,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!checkSettingsPermission(callerPid, callerUid)) { newNc.setUids(null); newNc.setSSID(null); - // TODO: Processes holding NETWORK_FACTORY should be able to see the underlying networks - newNc.setUnderlyingNetworks(null); } if (newNc.getNetworkSpecifier() != null) { newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact()); @@ -2265,6 +2270,7 @@ public class ConnectivityService extends IConnectivityManager.Stub newNc.setAllowedUids(new ArraySet<>()); newNc.setSubscriptionIds(Collections.emptySet()); } + redactUnderlyingNetworksForCapabilities(newNc, callerPid, callerUid); return newNc; } @@ -2858,12 +2864,16 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void enforceNetworkFactoryPermission() { + // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface. + if (getCallingUid() == Process.BLUETOOTH_UID) return; enforceAnyPermissionOf( android.Manifest.permission.NETWORK_FACTORY, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } private void enforceNetworkFactoryOrSettingsPermission() { + // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface. + if (getCallingUid() == Process.BLUETOOTH_UID) return; enforceAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_FACTORY, @@ -2871,12 +2881,24 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void enforceNetworkFactoryOrTestNetworksPermission() { + // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface. + if (getCallingUid() == Process.BLUETOOTH_UID) return; enforceAnyPermissionOf( android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_FACTORY, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } + private boolean checkNetworkFactoryOrSettingsPermission(int pid, int uid) { + return PERMISSION_GRANTED == mContext.checkPermission( + android.Manifest.permission.NETWORK_FACTORY, pid, uid) + || PERMISSION_GRANTED == mContext.checkPermission( + android.Manifest.permission.NETWORK_SETTINGS, pid, uid) + || PERMISSION_GRANTED == mContext.checkPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid) + || uid == Process.BLUETOOTH_UID; + } + private boolean checkSettingsPermission() { return checkAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java index b7617626d3..ce955fd6a4 100644 --- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java +++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java @@ -19,8 +19,6 @@ package com.android.server.connectivity; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_NOT_EXPORTED; - import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; @@ -160,7 +158,7 @@ public class CarrierPrivilegeAuthenticator extends BroadcastReceiver { private void registerForCarrierChanges() { final IntentFilter filter = new IntentFilter(); filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED); - mContext.registerReceiver(this, filter, null, mHandler, RECEIVER_NOT_EXPORTED /* flags */); + mContext.registerReceiver(this, filter, null, mHandler); registerCarrierPrivilegesListeners(); } diff --git a/service/src/com/android/server/connectivity/ConnectivityNativeService.java b/service/src/com/android/server/connectivity/ConnectivityNativeService.java new file mode 100644 index 0000000000..cde6ea75e2 --- /dev/null +++ b/service/src/com/android/server/connectivity/ConnectivityNativeService.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND; +import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.net.connectivity.aidl.ConnectivityNative; +import android.os.Binder; +import android.os.Process; +import android.os.ServiceSpecificException; +import android.system.ErrnoException; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.BpfBitmap; +import com.android.net.module.util.BpfUtils; +import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.PermissionUtils; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * @hide + */ +public class ConnectivityNativeService extends ConnectivityNative.Stub { + public static final String SERVICE_NAME = "connectivity_native"; + + private static final String TAG = ConnectivityNativeService.class.getSimpleName(); + private static final String CGROUP_PATH = "/sys/fs/cgroup"; + private static final String V4_PROG_PATH = + "/sys/fs/bpf/prog_block_bind4_block_port"; + private static final String V6_PROG_PATH = + "/sys/fs/bpf/prog_block_bind6_block_port"; + private static final String BLOCKED_PORTS_MAP_PATH = "/sys/fs/bpf/map_block_blocked_ports_map"; + + private final Context mContext; + + // BPF map for port blocking. Exactly 65536 entries long, with one entry per port number + @Nullable + private final BpfBitmap mBpfBlockedPortsMap; + + /** + * Dependencies of ConnectivityNativeService, for injection in tests. + */ + @VisibleForTesting + public static class Dependencies { + /** Get BPF maps. */ + @Nullable public BpfBitmap getBlockPortsMap() { + try { + return new BpfBitmap(BLOCKED_PORTS_MAP_PATH); + } catch (ErrnoException e) { + throw new UnsupportedOperationException("Failed to create blocked ports map: " + + e); + } + } + } + + private void enforceBlockPortPermission() { + final int uid = Binder.getCallingUid(); + if (uid == Process.ROOT_UID || uid == Process.PHONE_UID) return; + PermissionUtils.enforceNetworkStackPermission(mContext); + } + + private void ensureValidPortNumber(int port) { + if (port < 0 || port > 65535) { + throw new IllegalArgumentException("Invalid port number " + port); + } + } + + public ConnectivityNativeService(final Context context) { + this(context, new Dependencies()); + } + + @VisibleForTesting + protected ConnectivityNativeService(final Context context, @NonNull Dependencies deps) { + mContext = context; + mBpfBlockedPortsMap = deps.getBlockPortsMap(); + attachProgram(); + } + + @Override + public void blockPortForBind(int port) { + enforceBlockPortPermission(); + ensureValidPortNumber(port); + try { + mBpfBlockedPortsMap.set(port); + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, e.getMessage()); + } + } + + @Override + public void unblockPortForBind(int port) { + enforceBlockPortPermission(); + ensureValidPortNumber(port); + try { + mBpfBlockedPortsMap.unset(port); + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, + "Could not unset bitmap value for (port: " + port + "): " + e); + } + } + + @Override + public void unblockAllPortsForBind() { + enforceBlockPortPermission(); + try { + mBpfBlockedPortsMap.clear(); + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, "Could not clear map: " + e); + } + } + + @Override + public int[] getPortsBlockedForBind() { + enforceBlockPortPermission(); + + ArrayList<Integer> portMap = new ArrayList<Integer>(); + for (int i = 0; i <= 65535; i++) { + try { + if (mBpfBlockedPortsMap.get(i)) portMap.add(i); + } catch (ErrnoException e) { + Log.e(TAG, "Failed to get index " + i, e); + } + } + return CollectionUtils.toIntArray(portMap); + } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } + + /** + * Attach BPF program + */ + private void attachProgram() { + try { + BpfUtils.attachProgram(BPF_CGROUP_INET4_BIND, V4_PROG_PATH, CGROUP_PATH, 0); + } catch (IOException e) { + throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET4_BIND: " + + e); + } + try { + BpfUtils.attachProgram(BPF_CGROUP_INET6_BIND, V6_PROG_PATH, CGROUP_PATH, 0); + } catch (IOException e) { + throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET6_BIND: " + + e); + } + Log.d(TAG, "Attached BPF_CGROUP_INET4_BIND and BPF_CGROUP_INET6_BIND programs"); + } +} diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java index 799f46b524..b13ba9328a 100644 --- a/service/src/com/android/server/connectivity/FullScore.java +++ b/service/src/com/android/server/connectivity/FullScore.java @@ -29,6 +29,7 @@ import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkScore; import android.net.NetworkScore.KeepConnectedReason; +import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; @@ -46,6 +47,8 @@ import java.util.StringJoiner; * they are handling a score that had the CS-managed bits set. */ public class FullScore { + private static final String TAG = FullScore.class.getSimpleName(); + // This will be removed soon. Do *NOT* depend on it for any new code that is not part of // a migration. private final int mLegacyInt; @@ -126,7 +129,15 @@ public class FullScore { @VisibleForTesting static @NonNull String policyNameOf(final int policy) { final String name = sMessageNames.get(policy); - if (name == null) throw new IllegalArgumentException("Unknown policy: " + policy); + if (name == null) { + // Don't throw here because name might be null due to proguard stripping out the + // POLICY_* constants, potentially causing a crash only on user builds because proguard + // does not run on userdebug builds. + // TODO: make MessageUtils safer by not returning the array and instead storing it + // internally and providing a getter (that does not throw) for individual values. + Log.wtf(TAG, "Unknown policy: " + policy); + return Integer.toString(policy); + } return name.substring("POLICY_".length()); } diff --git a/service/src/com/android/server/net/DelayedDiskWrite.java b/service/src/com/android/server/net/DelayedDiskWrite.java new file mode 100644 index 0000000000..35dc455725 --- /dev/null +++ b/service/src/com/android/server/net/DelayedDiskWrite.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import android.os.Handler; +import android.os.HandlerThread; +import android.text.TextUtils; +import android.util.Log; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * This class provides APIs to do a delayed data write to a given {@link OutputStream}. + */ +public class DelayedDiskWrite { + private static final String TAG = "DelayedDiskWrite"; + + private HandlerThread mDiskWriteHandlerThread; + private Handler mDiskWriteHandler; + /* Tracks multiple writes on the same thread */ + private int mWriteSequence = 0; + + /** + * Used to do a delayed data write to a given {@link OutputStream}. + */ + public interface Writer { + /** + * write data to a given {@link OutputStream}. + */ + void onWriteCalled(DataOutputStream out) throws IOException; + } + + /** + * Do a delayed data write to a given output stream opened from filePath. + */ + public void write(final String filePath, final Writer w) { + write(filePath, w, true); + } + + /** + * Do a delayed data write to a given output stream opened from filePath. + */ + public void write(final String filePath, final Writer w, final boolean open) { + if (TextUtils.isEmpty(filePath)) { + throw new IllegalArgumentException("empty file path"); + } + + /* Do a delayed write to disk on a separate handler thread */ + synchronized (this) { + if (++mWriteSequence == 1) { + mDiskWriteHandlerThread = new HandlerThread("DelayedDiskWriteThread"); + mDiskWriteHandlerThread.start(); + mDiskWriteHandler = new Handler(mDiskWriteHandlerThread.getLooper()); + } + } + + mDiskWriteHandler.post(new Runnable() { + @Override + public void run() { + doWrite(filePath, w, open); + } + }); + } + + private void doWrite(String filePath, Writer w, boolean open) { + DataOutputStream out = null; + try { + if (open) { + out = new DataOutputStream(new BufferedOutputStream( + new FileOutputStream(filePath))); + } + w.onWriteCalled(out); + } catch (IOException e) { + loge("Error writing data file " + filePath); + } finally { + if (out != null) { + try { + out.close(); + } catch (Exception e) { } + } + + // Quit if no more writes sent + synchronized (this) { + if (--mWriteSequence == 0) { + mDiskWriteHandler.getLooper().quit(); + mDiskWriteHandler = null; + mDiskWriteHandlerThread = null; + } + } + } + } + + private void loge(String s) { + Log.e(TAG, s); + } +} + |
