blob: 9d228c3d0a6b41ad1a4b5f0aa180a47824d44360 [file] [log] [blame]
Nathan Harold1afbef42017-03-01 18:55:06 -08001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import static android.Manifest.permission.DUMP;
Nathan Harold93962f32017-03-07 13:23:36 -080020import static android.net.IpSecManager.INVALID_RESOURCE_ID;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070021import static android.system.OsConstants.AF_INET;
manojboopathic4be79d2017-11-30 17:11:49 -080022import static android.system.OsConstants.EINVAL;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070023import static android.system.OsConstants.IPPROTO_UDP;
24import static android.system.OsConstants.SOCK_DGRAM;
25import static com.android.internal.util.Preconditions.checkNotNull;
Nathan Harold1afbef42017-03-01 18:55:06 -080026
27import android.content.Context;
Nathan Harolda2523312018-01-05 19:25:13 -080028import android.net.ConnectivityManager;
Nathan Harold1afbef42017-03-01 18:55:06 -080029import android.net.IIpSecService;
30import android.net.INetd;
Nathan Harold93962f32017-03-07 13:23:36 -080031import android.net.IpSecAlgorithm;
32import android.net.IpSecConfig;
33import android.net.IpSecManager;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070034import android.net.IpSecSpiResponse;
Nathan Harold93962f32017-03-07 13:23:36 -080035import android.net.IpSecTransform;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070036import android.net.IpSecTransformResponse;
37import android.net.IpSecUdpEncapResponse;
Nathan Harolda10003d2017-08-23 13:46:33 -070038import android.net.NetworkUtils;
Benedict Wongbabe5d72017-12-03 19:42:36 -080039import android.net.TrafficStats;
Nathan Harold1afbef42017-03-01 18:55:06 -080040import android.net.util.NetdService;
Nathan Harold93962f32017-03-07 13:23:36 -080041import android.os.Binder;
Nathan Harold93962f32017-03-07 13:23:36 -080042import android.os.IBinder;
43import android.os.ParcelFileDescriptor;
Nathan Harold1afbef42017-03-01 18:55:06 -080044import android.os.RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -080045import android.os.ServiceSpecificException;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070046import android.system.ErrnoException;
47import android.system.Os;
48import android.system.OsConstants;
Nathan Harolda10003d2017-08-23 13:46:33 -070049import android.text.TextUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080050import android.util.Log;
51import android.util.Slog;
Nathan Harold93962f32017-03-07 13:23:36 -080052import android.util.SparseArray;
Nathan Harolda10003d2017-08-23 13:46:33 -070053
Nathan Harold93962f32017-03-07 13:23:36 -080054import com.android.internal.annotations.GuardedBy;
ludi1a06aa72017-05-12 09:15:00 -070055import com.android.internal.annotations.VisibleForTesting;
Benedict Wong4f255702017-11-06 20:49:10 -080056import com.android.internal.util.Preconditions;
Nathan Harolda10003d2017-08-23 13:46:33 -070057
Nathan Harold1afbef42017-03-01 18:55:06 -080058import java.io.FileDescriptor;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070059import java.io.IOException;
Nathan Harold1afbef42017-03-01 18:55:06 -080060import java.io.PrintWriter;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070061import java.net.InetAddress;
62import java.net.InetSocketAddress;
63import java.net.UnknownHostException;
Benedict Wong409c8ca2017-10-26 19:41:43 -070064import java.util.ArrayList;
65import java.util.List;
Nathan Harolda10003d2017-08-23 13:46:33 -070066
Nathan Harold8dc1fd02017-04-04 19:37:48 -070067import libcore.io.IoUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080068
Benedict Wong409c8ca2017-10-26 19:41:43 -070069/**
70 * A service to manage multiple clients that want to access the IpSec API. The service is
71 * responsible for maintaining a list of clients and managing the resources (and related quotas)
72 * that each of them own.
73 *
74 * <p>Synchronization in IpSecService is done on all entrypoints due to potential race conditions at
75 * the kernel/xfrm level. Further, this allows the simplifying assumption to be made that only one
76 * thread is ever running at a time.
77 *
78 * @hide
79 */
Nathan Harold1afbef42017-03-01 18:55:06 -080080public class IpSecService extends IIpSecService.Stub {
81 private static final String TAG = "IpSecService";
82 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -070083
Nathan Harold1afbef42017-03-01 18:55:06 -080084 private static final String NETD_SERVICE_NAME = "netd";
Nathan Harold93962f32017-03-07 13:23:36 -080085 private static final int[] DIRECTIONS =
Nathan Harolda2523312018-01-05 19:25:13 -080086 new int[] {IpSecManager.DIRECTION_OUT, IpSecManager.DIRECTION_IN};
Nathan Harold1afbef42017-03-01 18:55:06 -080087
ludi1a06aa72017-05-12 09:15:00 -070088 private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
Nathan Harold8dc1fd02017-04-04 19:37:48 -070089 private static final int MAX_PORT_BIND_ATTEMPTS = 10;
90 private static final InetAddress INADDR_ANY;
91
92 static {
93 try {
94 INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
95 } catch (UnknownHostException e) {
96 throw new RuntimeException(e);
97 }
98 }
99
100 static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
101 static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
102
103 /* Binder context for this service */
Nathan Harold1afbef42017-03-01 18:55:06 -0800104 private final Context mContext;
105
Nathan Haroldd8c74292017-12-13 19:16:33 -0800106 /**
Nathan Harolda2523312018-01-05 19:25:13 -0800107 * The next non-repeating global ID for tracking resources between users, this service, and
108 * kernel data structures. Accessing this variable is not thread safe, so it is only read or
109 * modified within blocks synchronized on IpSecService.this. We want to avoid -1
110 * (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
Nathan Haroldd8c74292017-12-13 19:16:33 -0800111 */
112 @GuardedBy("IpSecService.this")
113 private int mNextResourceId = 1;
Nathan Harold1afbef42017-03-01 18:55:06 -0800114
ludi1a06aa72017-05-12 09:15:00 -0700115 interface IpSecServiceConfiguration {
116 INetd getNetdInstance() throws RemoteException;
117
118 static IpSecServiceConfiguration GETSRVINSTANCE =
119 new IpSecServiceConfiguration() {
120 @Override
121 public INetd getNetdInstance() throws RemoteException {
122 final INetd netd = NetdService.getInstance();
123 if (netd == null) {
124 throw new RemoteException("Failed to Get Netd Instance");
125 }
126 return netd;
127 }
128 };
129 }
130
131 private final IpSecServiceConfiguration mSrvConfig;
Benedict Wongbabe5d72017-12-03 19:42:36 -0800132 final UidFdTagger mUidFdTagger;
ludi1a06aa72017-05-12 09:15:00 -0700133
Benedict Wong409c8ca2017-10-26 19:41:43 -0700134 /**
135 * Interface for user-reference and kernel-resource cleanup.
136 *
137 * <p>This interface must be implemented for a resource to be reference counted.
138 */
139 @VisibleForTesting
140 public interface IResource {
141 /**
142 * Invalidates a IResource object, ensuring it is invalid for the purposes of allocating new
143 * objects dependent on it.
144 *
145 * <p>Implementations of this method are expected to remove references to the IResource
146 * object from the IpSecService's tracking arrays. The removal from the arrays ensures that
147 * the resource is considered invalid for user access or allocation or use in other
148 * resources.
149 *
150 * <p>References to the IResource object may be held by other RefcountedResource objects,
151 * and as such, the kernel resources and quota may not be cleaned up.
152 */
153 void invalidate() throws RemoteException;
154
155 /**
156 * Releases underlying resources and related quotas.
157 *
158 * <p>Implementations of this method are expected to remove all system resources that are
159 * tracked by the IResource object. Due to other RefcountedResource objects potentially
Benedict Wong344bd622017-11-16 15:27:22 -0800160 * having references to the IResource object, freeUnderlyingResources may not always be
Benedict Wong409c8ca2017-10-26 19:41:43 -0700161 * called from releaseIfUnreferencedRecursively().
162 */
163 void freeUnderlyingResources() throws RemoteException;
164 }
165
166 /**
167 * RefcountedResource manages references and dependencies in an exclusively acyclic graph.
168 *
169 * <p>RefcountedResource implements both explicit and implicit resource management. Creating a
170 * RefcountedResource object creates an explicit reference that must be freed by calling
171 * userRelease(). Additionally, adding this object as a child of another RefcountedResource
172 * object will add an implicit reference.
173 *
174 * <p>Resources are cleaned up when all references, both implicit and explicit, are released
175 * (ie, when userRelease() is called and when all parents have called releaseReference() on this
176 * object.)
177 */
178 @VisibleForTesting
179 public class RefcountedResource<T extends IResource> implements IBinder.DeathRecipient {
180 private final T mResource;
181 private final List<RefcountedResource> mChildren;
182 int mRefCount = 1; // starts at 1 for user's reference.
183 IBinder mBinder;
184
185 RefcountedResource(T resource, IBinder binder, RefcountedResource... children) {
186 synchronized (IpSecService.this) {
187 this.mResource = resource;
188 this.mChildren = new ArrayList<>(children.length);
189 this.mBinder = binder;
190
191 for (RefcountedResource child : children) {
192 mChildren.add(child);
193 child.mRefCount++;
194 }
195
196 try {
197 mBinder.linkToDeath(this, 0);
198 } catch (RemoteException e) {
199 binderDied();
200 }
201 }
202 }
203
204 /**
205 * If the Binder object dies, this function is called to free the system resources that are
Benedict Wong344bd622017-11-16 15:27:22 -0800206 * being tracked by this record and to subsequently release this record for garbage
Benedict Wong409c8ca2017-10-26 19:41:43 -0700207 * collection
208 */
209 @Override
210 public void binderDied() {
211 synchronized (IpSecService.this) {
212 try {
213 userRelease();
214 } catch (Exception e) {
215 Log.e(TAG, "Failed to release resource: " + e);
216 }
217 }
218 }
219
220 public T getResource() {
221 return mResource;
222 }
223
224 /**
225 * Unlinks from binder and performs IpSecService resource cleanup (removes from resource
226 * arrays)
227 *
228 * <p>If this method has been previously called, the RefcountedResource's binder field will
229 * be null, and the method will return without performing the cleanup a second time.
230 *
231 * <p>Note that calling this function does not imply that kernel resources will be freed at
232 * this time, or that the related quota will be returned. Such actions will only be
233 * performed upon the reference count reaching zero.
234 */
235 @GuardedBy("IpSecService.this")
236 public void userRelease() throws RemoteException {
237 // Prevent users from putting reference counts into a bad state by calling
238 // userRelease() multiple times.
239 if (mBinder == null) {
240 return;
241 }
242
243 mBinder.unlinkToDeath(this, 0);
244 mBinder = null;
245
246 mResource.invalidate();
247
248 releaseReference();
249 }
250
251 /**
252 * Removes a reference to this resource. If the resultant reference count is zero, the
253 * underlying resources are freed, and references to all child resources are also dropped
254 * recursively (resulting in them freeing their resources and children, etcetera)
255 *
256 * <p>This method also sets the reference count to an invalid value (-1) to signify that it
257 * has been fully released. Any subsequent calls to this method will result in an
258 * IllegalStateException being thrown due to resource already having been previously
259 * released
260 */
261 @VisibleForTesting
262 @GuardedBy("IpSecService.this")
263 public void releaseReference() throws RemoteException {
264 mRefCount--;
265
266 if (mRefCount > 0) {
267 return;
268 } else if (mRefCount < 0) {
269 throw new IllegalStateException(
270 "Invalid operation - resource has already been released.");
271 }
272
273 // Cleanup own resources
274 mResource.freeUnderlyingResources();
275
276 // Cleanup child resources as needed
277 for (RefcountedResource<? extends IResource> child : mChildren) {
278 child.releaseReference();
279 }
280
281 // Enforce that resource cleanup can only be called once
282 // By decrementing the refcount (from 0 to -1), the next call will throw an
283 // IllegalStateException - it has already been released fully.
284 mRefCount--;
285 }
286
287 @Override
288 public String toString() {
289 return new StringBuilder()
290 .append("{mResource=")
291 .append(mResource)
292 .append(", mRefCount=")
293 .append(mRefCount)
294 .append(", mChildren=")
295 .append(mChildren)
296 .append("}")
297 .toString();
298 }
299 }
300
Nathan Harolda1afbd82017-04-24 16:16:34 -0700301 /* Very simple counting class that looks much like a counting semaphore */
Benedict Wong344bd622017-11-16 15:27:22 -0800302 @VisibleForTesting
303 static class ResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700304 private final int mMax;
305 int mCurrent;
306
307 ResourceTracker(int max) {
308 mMax = max;
309 mCurrent = 0;
310 }
311
Benedict Wong344bd622017-11-16 15:27:22 -0800312 boolean isAvailable() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700313 return (mCurrent < mMax);
314 }
315
Benedict Wong344bd622017-11-16 15:27:22 -0800316 void take() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700317 if (!isAvailable()) {
318 Log.wtf(TAG, "Too many resources allocated!");
319 }
320 mCurrent++;
321 }
322
Benedict Wong344bd622017-11-16 15:27:22 -0800323 void give() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700324 if (mCurrent <= 0) {
325 Log.wtf(TAG, "We've released this resource too many times");
326 }
327 mCurrent--;
328 }
ludi3e5ea232017-08-10 15:44:40 -0700329
330 @Override
331 public String toString() {
332 return new StringBuilder()
333 .append("{mCurrent=")
334 .append(mCurrent)
335 .append(", mMax=")
336 .append(mMax)
337 .append("}")
338 .toString();
339 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700340 }
341
Benedict Wong344bd622017-11-16 15:27:22 -0800342 @VisibleForTesting
343 static final class UserRecord {
344 /* Type names */
345 public static final String TYPENAME_SPI = "SecurityParameterIndex";
346 public static final String TYPENAME_TRANSFORM = "IpSecTransform";
347 public static final String TYPENAME_ENCAP_SOCKET = "UdpEncapSocket";
348
349 /* Maximum number of each type of resource that a single UID may possess */
Nathan Harolda1afbd82017-04-24 16:16:34 -0700350 public static final int MAX_NUM_ENCAP_SOCKETS = 2;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700351 public static final int MAX_NUM_TRANSFORMS = 4;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700352 public static final int MAX_NUM_SPIS = 8;
353
Benedict Wong344bd622017-11-16 15:27:22 -0800354 final RefcountedResourceArray<SpiRecord> mSpiRecords =
355 new RefcountedResourceArray<>(TYPENAME_SPI);
356 final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
ludi3e5ea232017-08-10 15:44:40 -0700357
Benedict Wong344bd622017-11-16 15:27:22 -0800358 final RefcountedResourceArray<TransformRecord> mTransformRecords =
359 new RefcountedResourceArray<>(TYPENAME_TRANSFORM);
360 final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
361
362 final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =
363 new RefcountedResourceArray<>(TYPENAME_ENCAP_SOCKET);
364 final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
365
366 void removeSpiRecord(int resourceId) {
367 mSpiRecords.remove(resourceId);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700368 }
369
Benedict Wong344bd622017-11-16 15:27:22 -0800370 void removeTransformRecord(int resourceId) {
371 mTransformRecords.remove(resourceId);
372 }
373
374 void removeEncapSocketRecord(int resourceId) {
375 mEncapSocketRecords.remove(resourceId);
376 }
377
378 @Override
379 public String toString() {
380 return new StringBuilder()
381 .append("{mSpiQuotaTracker=")
382 .append(mSpiQuotaTracker)
383 .append(", mTransformQuotaTracker=")
384 .append(mTransformQuotaTracker)
385 .append(", mSocketQuotaTracker=")
386 .append(mSocketQuotaTracker)
387 .append(", mSpiRecords=")
388 .append(mSpiRecords)
389 .append(", mTransformRecords=")
390 .append(mTransformRecords)
391 .append(", mEncapSocketRecords=")
392 .append(mEncapSocketRecords)
393 .append("}")
394 .toString();
395 }
396 }
397
398 @VisibleForTesting
399 static final class UserResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700400 private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
401
Benedict Wong344bd622017-11-16 15:27:22 -0800402 /** Never-fail getter that populates the list of UIDs as-needed */
403 public UserRecord getUserRecord(int uid) {
404 checkCallerUid(uid);
405
Nathan Harolda1afbd82017-04-24 16:16:34 -0700406 UserRecord r = mUserRecords.get(uid);
407 if (r == null) {
408 r = new UserRecord();
409 mUserRecords.put(uid, r);
410 }
411 return r;
412 }
ludi3e5ea232017-08-10 15:44:40 -0700413
Benedict Wong344bd622017-11-16 15:27:22 -0800414 /** Safety method; guards against access of other user's UserRecords */
415 private void checkCallerUid(int uid) {
416 if (uid != Binder.getCallingUid()
417 && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
418 throw new SecurityException("Attempted access of unowned resources");
419 }
420 }
421
ludi3e5ea232017-08-10 15:44:40 -0700422 @Override
423 public String toString() {
424 return mUserRecords.toString();
425 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700426 }
427
Benedict Wong344bd622017-11-16 15:27:22 -0800428 @VisibleForTesting final UserResourceTracker mUserResourceTracker = new UserResourceTracker();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700429
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700430 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800431 * The KernelResourceRecord class provides a facility to cleanly and reliably track system
432 * resources. It relies on a provided resourceId that should uniquely identify the kernel
433 * resource. To use this class, the user should implement the invalidate() and
434 * freeUnderlyingResources() methods that are responsible for cleaning up IpSecService resource
435 * tracking arrays and kernel resources, respectively
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700436 */
Benedict Wong344bd622017-11-16 15:27:22 -0800437 private abstract class KernelResourceRecord implements IResource {
Nathan Harold93962f32017-03-07 13:23:36 -0800438 final int pid;
439 final int uid;
Benedict Wong344bd622017-11-16 15:27:22 -0800440 protected final int mResourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800441
Benedict Wong344bd622017-11-16 15:27:22 -0800442 KernelResourceRecord(int resourceId) {
Nathan Harold93962f32017-03-07 13:23:36 -0800443 super();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700444 if (resourceId == INVALID_RESOURCE_ID) {
445 throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
446 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700447 mResourceId = resourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800448 pid = Binder.getCallingPid();
449 uid = Binder.getCallingUid();
450
Nathan Harolda1afbd82017-04-24 16:16:34 -0700451 getResourceTracker().take();
Nathan Harold93962f32017-03-07 13:23:36 -0800452 }
453
Benedict Wong344bd622017-11-16 15:27:22 -0800454 @Override
455 public abstract void invalidate() throws RemoteException;
456
457 /** Convenience method; retrieves the user resource record for the stored UID. */
458 protected UserRecord getUserRecord() {
459 return mUserResourceTracker.getUserRecord(uid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700460 }
461
Benedict Wong344bd622017-11-16 15:27:22 -0800462 @Override
463 public abstract void freeUnderlyingResources() throws RemoteException;
ludib0c95b12017-05-22 10:52:23 -0700464
Nathan Harolda1afbd82017-04-24 16:16:34 -0700465 /** Get the resource tracker for this resource */
466 protected abstract ResourceTracker getResourceTracker();
467
ludib0c95b12017-05-22 10:52:23 -0700468 @Override
469 public String toString() {
470 return new StringBuilder()
471 .append("{mResourceId=")
472 .append(mResourceId)
473 .append(", pid=")
474 .append(pid)
475 .append(", uid=")
476 .append(uid)
ludib0c95b12017-05-22 10:52:23 -0700477 .append("}")
478 .toString();
479 }
Nathan Harold93962f32017-03-07 13:23:36 -0800480 };
481
Benedict Wong344bd622017-11-16 15:27:22 -0800482 // TODO: Move this to right after RefcountedResource. With this here, Gerrit was showing many
483 // more things as changed.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700484 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800485 * Thin wrapper over SparseArray to ensure resources exist, and simplify generic typing.
486 *
487 * <p>RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException
488 * if a key is not found during a retrieval process.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700489 */
Benedict Wong344bd622017-11-16 15:27:22 -0800490 static class RefcountedResourceArray<T extends IResource> {
491 SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
492 private final String mTypeName;
Nathan Harold93962f32017-03-07 13:23:36 -0800493
Benedict Wong344bd622017-11-16 15:27:22 -0800494 public RefcountedResourceArray(String typeName) {
495 this.mTypeName = typeName;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700496 }
497
Benedict Wong344bd622017-11-16 15:27:22 -0800498 /**
499 * Accessor method to get inner resource object.
500 *
501 * @throws IllegalArgumentException if no resource with provided key is found.
502 */
503 T getResourceOrThrow(int key) {
504 return getRefcountedResourceOrThrow(key).getResource();
505 }
506
507 /**
508 * Accessor method to get reference counting wrapper.
509 *
510 * @throws IllegalArgumentException if no resource with provided key is found.
511 */
512 RefcountedResource<T> getRefcountedResourceOrThrow(int key) {
513 RefcountedResource<T> resource = mArray.get(key);
514 if (resource == null) {
515 throw new IllegalArgumentException(
516 String.format("No such %s found for given id: %d", mTypeName, key));
517 }
518
519 return resource;
520 }
521
522 void put(int key, RefcountedResource<T> obj) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700523 checkNotNull(obj, "Null resources cannot be added");
524 mArray.put(key, obj);
525 }
526
527 void remove(int key) {
528 mArray.remove(key);
529 }
ludib0c95b12017-05-22 10:52:23 -0700530
531 @Override
532 public String toString() {
533 return mArray.toString();
534 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700535 }
536
Benedict Wong344bd622017-11-16 15:27:22 -0800537 private final class TransformRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700538 private final IpSecConfig mConfig;
Nathan Harolda2523312018-01-05 19:25:13 -0800539 private final SpiRecord mSpi;
Benedict Wong344bd622017-11-16 15:27:22 -0800540 private final EncapSocketRecord mSocket;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700541
542 TransformRecord(
Nathan Harolda2523312018-01-05 19:25:13 -0800543 int resourceId, IpSecConfig config, SpiRecord spi, EncapSocketRecord socket) {
Benedict Wong344bd622017-11-16 15:27:22 -0800544 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800545 mConfig = config;
Nathan Harolda2523312018-01-05 19:25:13 -0800546 mSpi = spi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700547 mSocket = socket;
Nathan Harold93962f32017-03-07 13:23:36 -0800548 }
549
550 public IpSecConfig getConfig() {
551 return mConfig;
552 }
553
Nathan Harolda2523312018-01-05 19:25:13 -0800554 public SpiRecord getSpiRecord() {
555 return mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700556 }
557
558 /** always guarded by IpSecService#this */
Nathan Harold93962f32017-03-07 13:23:36 -0800559 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800560 public void freeUnderlyingResources() {
Nathan Harolda2523312018-01-05 19:25:13 -0800561 int spi = mSpi.getSpi();
562 try {
563 mSrvConfig
564 .getNetdInstance()
565 .ipSecDeleteSecurityAssociation(
566 mResourceId,
567 mConfig.getSourceAddress(),
568 mConfig.getDestinationAddress(),
569 spi);
570 } catch (ServiceSpecificException e) {
571 // FIXME: get the error code and throw is at an IOException from Errno Exception
572 } catch (RemoteException e) {
573 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800574 }
Nathan Harold93962f32017-03-07 13:23:36 -0800575
Benedict Wong344bd622017-11-16 15:27:22 -0800576 getResourceTracker().give();
Nathan Harold93962f32017-03-07 13:23:36 -0800577 }
ludib0c95b12017-05-22 10:52:23 -0700578
Benedict Wong344bd622017-11-16 15:27:22 -0800579 @Override
580 public void invalidate() throws RemoteException {
581 getUserRecord().removeTransformRecord(mResourceId);
582 }
583
584 @Override
Nathan Harolda1afbd82017-04-24 16:16:34 -0700585 protected ResourceTracker getResourceTracker() {
Benedict Wong344bd622017-11-16 15:27:22 -0800586 return getUserRecord().mTransformQuotaTracker;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700587 }
588
ludib0c95b12017-05-22 10:52:23 -0700589 @Override
590 public String toString() {
591 StringBuilder strBuilder = new StringBuilder();
592 strBuilder
593 .append("{super=")
594 .append(super.toString())
595 .append(", mSocket=")
596 .append(mSocket)
Nathan Harolda2523312018-01-05 19:25:13 -0800597 .append(", mSpi.mResourceId=")
598 .append(mSpi.mResourceId)
ludib0c95b12017-05-22 10:52:23 -0700599 .append(", mConfig=")
600 .append(mConfig)
601 .append("}");
602 return strBuilder.toString();
603 }
Nathan Harold93962f32017-03-07 13:23:36 -0800604 }
605
Benedict Wong344bd622017-11-16 15:27:22 -0800606 private final class SpiRecord extends KernelResourceRecord {
Nathan Harolda2523312018-01-05 19:25:13 -0800607 private final String mSourceAddress;
608 private final String mDestinationAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800609 private int mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700610
611 private boolean mOwnedByTransform = false;
Nathan Harold93962f32017-03-07 13:23:36 -0800612
Nathan Harolda2523312018-01-05 19:25:13 -0800613 SpiRecord(int resourceId, String sourceAddress, String destinationAddress, int spi) {
Benedict Wong344bd622017-11-16 15:27:22 -0800614 super(resourceId);
Nathan Harolda2523312018-01-05 19:25:13 -0800615 mSourceAddress = sourceAddress;
616 mDestinationAddress = destinationAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800617 mSpi = spi;
Nathan Harold93962f32017-03-07 13:23:36 -0800618 }
619
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700620 /** always guarded by IpSecService#this */
621 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800622 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700623 if (mOwnedByTransform) {
624 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
625 // Because SPIs are "handed off" to transform, objects, they should never be
626 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
627 // thus ownership and responsibility for freeing these resources passes to the
628 // Transform object). Thus, we should let the user free them without penalty once
629 // they are applied in a Transform object.
630 return;
631 }
632
Nathan Harold93962f32017-03-07 13:23:36 -0800633 try {
ludi1a06aa72017-05-12 09:15:00 -0700634 mSrvConfig
635 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800636 .ipSecDeleteSecurityAssociation(
Nathan Harolda2523312018-01-05 19:25:13 -0800637 mResourceId, mSourceAddress, mDestinationAddress, mSpi);
Nathan Harold93962f32017-03-07 13:23:36 -0800638 } catch (ServiceSpecificException e) {
639 // FIXME: get the error code and throw is at an IOException from Errno Exception
640 } catch (RemoteException e) {
641 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
642 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700643
644 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800645
Benedict Wong344bd622017-11-16 15:27:22 -0800646 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700647 }
648
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700649 public int getSpi() {
650 return mSpi;
651 }
652
Nathan Harolda2523312018-01-05 19:25:13 -0800653 public String getDestinationAddress() {
654 return mDestinationAddress;
655 }
656
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700657 public void setOwnedByTransform() {
658 if (mOwnedByTransform) {
659 // Programming error
Andreas Gamped6d8e452017-07-11 10:25:09 -0700660 throw new IllegalStateException("Cannot own an SPI twice!");
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700661 }
662
663 mOwnedByTransform = true;
Nathan Harold93962f32017-03-07 13:23:36 -0800664 }
ludib0c95b12017-05-22 10:52:23 -0700665
666 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800667 public void invalidate() throws RemoteException {
668 getUserRecord().removeSpiRecord(mResourceId);
669 }
670
671 @Override
672 protected ResourceTracker getResourceTracker() {
673 return getUserRecord().mSpiQuotaTracker;
674 }
675
676 @Override
ludib0c95b12017-05-22 10:52:23 -0700677 public String toString() {
678 StringBuilder strBuilder = new StringBuilder();
679 strBuilder
680 .append("{super=")
681 .append(super.toString())
682 .append(", mSpi=")
683 .append(mSpi)
Nathan Harolda2523312018-01-05 19:25:13 -0800684 .append(", mSourceAddress=")
685 .append(mSourceAddress)
686 .append(", mDestinationAddress=")
687 .append(mDestinationAddress)
ludib0c95b12017-05-22 10:52:23 -0700688 .append(", mOwnedByTransform=")
689 .append(mOwnedByTransform)
690 .append("}");
691 return strBuilder.toString();
692 }
Nathan Harold93962f32017-03-07 13:23:36 -0800693 }
694
Benedict Wong344bd622017-11-16 15:27:22 -0800695 private final class EncapSocketRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700696 private FileDescriptor mSocket;
697 private final int mPort;
Nathan Harold93962f32017-03-07 13:23:36 -0800698
Benedict Wong344bd622017-11-16 15:27:22 -0800699 EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
700 super(resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700701 mSocket = socket;
702 mPort = port;
703 }
704
705 /** always guarded by IpSecService#this */
706 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800707 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700708 Log.d(TAG, "Closing port " + mPort);
709 IoUtils.closeQuietly(mSocket);
710 mSocket = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700711
Benedict Wong344bd622017-11-16 15:27:22 -0800712 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700713 }
714
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700715 public int getPort() {
716 return mPort;
717 }
718
719 public FileDescriptor getSocket() {
720 return mSocket;
721 }
ludib0c95b12017-05-22 10:52:23 -0700722
723 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800724 protected ResourceTracker getResourceTracker() {
725 return getUserRecord().mSocketQuotaTracker;
726 }
727
728 @Override
729 public void invalidate() {
730 getUserRecord().removeEncapSocketRecord(mResourceId);
731 }
732
733 @Override
ludib0c95b12017-05-22 10:52:23 -0700734 public String toString() {
735 return new StringBuilder()
736 .append("{super=")
737 .append(super.toString())
738 .append(", mSocket=")
739 .append(mSocket)
740 .append(", mPort=")
741 .append(mPort)
742 .append("}")
743 .toString();
744 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700745 }
Nathan Harold93962f32017-03-07 13:23:36 -0800746
Nathan Harold1afbef42017-03-01 18:55:06 -0800747 /**
748 * Constructs a new IpSecService instance
749 *
750 * @param context Binder context for this service
751 */
752 private IpSecService(Context context) {
ludi1a06aa72017-05-12 09:15:00 -0700753 this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
Nathan Harold1afbef42017-03-01 18:55:06 -0800754 }
755
756 static IpSecService create(Context context) throws InterruptedException {
757 final IpSecService service = new IpSecService(context);
758 service.connectNativeNetdService();
759 return service;
760 }
761
ludi1a06aa72017-05-12 09:15:00 -0700762 /** @hide */
763 @VisibleForTesting
764 public IpSecService(Context context, IpSecServiceConfiguration config) {
Nathan Harolda2523312018-01-05 19:25:13 -0800765 this(
766 context,
767 config,
768 (fd, uid) -> {
769 try {
770 TrafficStats.setThreadStatsUid(uid);
771 TrafficStats.tagFileDescriptor(fd);
772 } finally {
773 TrafficStats.clearThreadStatsUid();
774 }
775 });
Benedict Wongbabe5d72017-12-03 19:42:36 -0800776 }
777
778 /** @hide */
779 @VisibleForTesting
780 public IpSecService(
781 Context context, IpSecServiceConfiguration config, UidFdTagger uidFdTagger) {
ludi1a06aa72017-05-12 09:15:00 -0700782 mContext = context;
783 mSrvConfig = config;
Benedict Wongbabe5d72017-12-03 19:42:36 -0800784 mUidFdTagger = uidFdTagger;
ludi1a06aa72017-05-12 09:15:00 -0700785 }
786
Nathan Harold1afbef42017-03-01 18:55:06 -0800787 public void systemReady() {
788 if (isNetdAlive()) {
789 Slog.d(TAG, "IpSecService is ready");
790 } else {
791 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
792 }
793 }
794
795 private void connectNativeNetdService() {
796 // Avoid blocking the system server to do this
Nathan Haroldb0e05082017-07-17 14:01:53 -0700797 new Thread() {
798 @Override
799 public void run() {
800 synchronized (IpSecService.this) {
ludi1a06aa72017-05-12 09:15:00 -0700801 NetdService.get(NETD_FETCH_TIMEOUT_MS);
Nathan Haroldb0e05082017-07-17 14:01:53 -0700802 }
803 }
804 }.start();
Nathan Harold1afbef42017-03-01 18:55:06 -0800805 }
806
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700807 synchronized boolean isNetdAlive() {
808 try {
ludi1a06aa72017-05-12 09:15:00 -0700809 final INetd netd = mSrvConfig.getNetdInstance();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700810 if (netd == null) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800811 return false;
812 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700813 return netd.isAlive();
814 } catch (RemoteException re) {
815 return false;
Nathan Harold1afbef42017-03-01 18:55:06 -0800816 }
817 }
818
Nathan Harolda10003d2017-08-23 13:46:33 -0700819 /**
820 * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
821 * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
822 */
823 private static void checkInetAddress(String inetAddress) {
824 if (TextUtils.isEmpty(inetAddress)) {
825 throw new IllegalArgumentException("Unspecified address");
826 }
827
828 InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
829
830 if (checkAddr.isAnyLocalAddress()) {
831 throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
832 }
833 }
834
835 /**
836 * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
837 * DIRECTION_IN or DIRECTION_OUT
838 */
839 private static void checkDirection(int direction) {
840 switch (direction) {
Nathan Harolda2523312018-01-05 19:25:13 -0800841 case IpSecManager.DIRECTION_OUT:
842 case IpSecManager.DIRECTION_IN:
Nathan Harolda10003d2017-08-23 13:46:33 -0700843 return;
844 }
845 throw new IllegalArgumentException("Invalid Direction: " + direction);
846 }
847
Nathan Harold93962f32017-03-07 13:23:36 -0800848 /** Get a new SPI and maintain the reservation in the system server */
Jonathan Basseri5fb92902017-11-16 10:58:01 -0800849 @Override
850 public synchronized IpSecSpiResponse allocateSecurityParameterIndex(
Nathan Harolda2523312018-01-05 19:25:13 -0800851 String destinationAddress, int requestedSpi, IBinder binder) throws RemoteException {
852 checkInetAddress(destinationAddress);
Nathan Harolda10003d2017-08-23 13:46:33 -0700853 /* requestedSpi can be anything in the int range, so no check is needed. */
Jonathan Basseri5fb92902017-11-16 10:58:01 -0800854 checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
Nathan Harolda10003d2017-08-23 13:46:33 -0700855
Benedict Wong344bd622017-11-16 15:27:22 -0800856 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Haroldd8c74292017-12-13 19:16:33 -0800857 final int resourceId = mNextResourceId++;
Nathan Harold93962f32017-03-07 13:23:36 -0800858
859 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800860 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800861 if (!userRecord.mSpiQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700862 return new IpSecSpiResponse(
Nathan Harolda10003d2017-08-23 13:46:33 -0700863 IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700864 }
Nathan Harolda2523312018-01-05 19:25:13 -0800865
Nathan Harold93962f32017-03-07 13:23:36 -0800866 spi =
ludi1a06aa72017-05-12 09:15:00 -0700867 mSrvConfig
868 .getNetdInstance()
Nathan Harolda2523312018-01-05 19:25:13 -0800869 .ipSecAllocateSpi(resourceId, "", destinationAddress, requestedSpi);
Nathan Harold93962f32017-03-07 13:23:36 -0800870 Log.d(TAG, "Allocated SPI " + spi);
Benedict Wong344bd622017-11-16 15:27:22 -0800871 userRecord.mSpiRecords.put(
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700872 resourceId,
Benedict Wong344bd622017-11-16 15:27:22 -0800873 new RefcountedResource<SpiRecord>(
Nathan Harolda2523312018-01-05 19:25:13 -0800874 new SpiRecord(resourceId, "", destinationAddress, spi), binder));
Nathan Harold93962f32017-03-07 13:23:36 -0800875 } catch (ServiceSpecificException e) {
876 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700877 return new IpSecSpiResponse(
Nathan Harolda1afbd82017-04-24 16:16:34 -0700878 IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800879 } catch (RemoteException e) {
880 throw e.rethrowFromSystemServer();
881 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700882 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
883 }
884
885 /* This method should only be called from Binder threads. Do not call this from
886 * within the system server as it will crash the system on failure.
887 */
Benedict Wong344bd622017-11-16 15:27:22 -0800888 private void releaseResource(RefcountedResourceArray resArray, int resourceId)
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700889 throws RemoteException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700890
Benedict Wong344bd622017-11-16 15:27:22 -0800891 resArray.getRefcountedResourceOrThrow(resourceId).userRelease();
Nathan Harold93962f32017-03-07 13:23:36 -0800892 }
893
894 /** Release a previously allocated SPI that has been registered with the system server */
895 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800896 public synchronized void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
897 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
898 releaseResource(userRecord.mSpiRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700899 }
900
901 /**
902 * This function finds and forcibly binds to a random system port, ensuring that the port cannot
903 * be unbound.
904 *
905 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
906 * a random open port and then bind by number, this function creates a temp socket, binds to a
907 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
908 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
909 * FileHandle.
910 *
911 * <p>The loop in this function handles the inherent race window between un-binding to a port
912 * and re-binding, during which the system could *technically* hand that port out to someone
913 * else.
914 */
Benedict Wongf186d672017-10-10 20:44:28 -0700915 private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700916 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
917 try {
918 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
919 Os.bind(probeSocket, INADDR_ANY, 0);
920 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
921 Os.close(probeSocket);
922 Log.v(TAG, "Binding to port " + port);
923 Os.bind(sockFd, INADDR_ANY, port);
Benedict Wongf186d672017-10-10 20:44:28 -0700924 return port;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700925 } catch (ErrnoException e) {
926 // Someone miraculously claimed the port just after we closed probeSocket.
927 if (e.errno == OsConstants.EADDRINUSE) {
928 continue;
929 }
930 throw e.rethrowAsIOException();
931 }
932 }
933 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
934 }
Nathan Harold93962f32017-03-07 13:23:36 -0800935
936 /**
Benedict Wongbabe5d72017-12-03 19:42:36 -0800937 * Functional interface to do traffic tagging of given sockets to UIDs.
938 *
939 * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
940 * sockets are billed to the UID that the UDP encap socket was created on behalf of.
941 *
942 * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
943 * methods that cannot be easily mocked/tested.
944 */
945 @VisibleForTesting
946 public interface UidFdTagger {
947 /**
948 * Sets socket tag to assign all traffic to the provided UID.
949 *
950 * <p>Since the socket is created on behalf of an unprivileged application, all traffic
951 * should be accounted to the UID of the unprivileged application.
952 */
953 public void tag(FileDescriptor fd, int uid) throws IOException;
954 }
955
956 /**
Nathan Harold93962f32017-03-07 13:23:36 -0800957 * Open a socket via the system server and bind it to the specified port (random if port=0).
958 * This will return a PFD to the user that represent a bound UDP socket. The system server will
959 * cache the socket and a record of its owner so that it can and must be freed when no longer
960 * needed.
961 */
962 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700963 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
964 throws RemoteException {
965 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
966 throw new IllegalArgumentException(
967 "Specified port number must be a valid non-reserved UDP port");
968 }
Nathan Harolda10003d2017-08-23 13:46:33 -0700969 checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
970
Benedict Wongbabe5d72017-12-03 19:42:36 -0800971 int callingUid = Binder.getCallingUid();
972 UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
Nathan Haroldd8c74292017-12-13 19:16:33 -0800973 final int resourceId = mNextResourceId++;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700974 FileDescriptor sockFd = null;
975 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800976 if (!userRecord.mSocketQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700977 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
978 }
979
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700980 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Benedict Wongbabe5d72017-12-03 19:42:36 -0800981 mUidFdTagger.tag(sockFd, callingUid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700982
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700983 // This code is common to both the unspecified and specified port cases
984 Os.setsockoptInt(
985 sockFd,
986 OsConstants.IPPROTO_UDP,
987 OsConstants.UDP_ENCAP,
988 OsConstants.UDP_ENCAP_ESPINUDP);
989
Benedict Wongba8d3132017-12-06 21:56:35 -0800990 mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
991 if (port != 0) {
992 Log.v(TAG, "Binding to port " + port);
993 Os.bind(sockFd, INADDR_ANY, port);
994 } else {
995 port = bindToRandomPort(sockFd);
996 }
997
Benedict Wong344bd622017-11-16 15:27:22 -0800998 userRecord.mEncapSocketRecords.put(
999 resourceId,
1000 new RefcountedResource<EncapSocketRecord>(
1001 new EncapSocketRecord(resourceId, sockFd, port), binder));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001002 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
1003 } catch (IOException | ErrnoException e) {
1004 IoUtils.closeQuietly(sockFd);
1005 }
1006 // If we make it to here, then something has gone wrong and we couldn't open a socket.
1007 // The only reasonable condition that would cause that is resource unavailable.
1008 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001009 }
1010
1011 /** close a socket that has been been allocated by and registered with the system server */
1012 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001013 public synchronized void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
1014 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1015 releaseResource(userRecord.mEncapSocketRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001016 }
Nathan Harold93962f32017-03-07 13:23:36 -08001017
Benedict Wong4f255702017-11-06 20:49:10 -08001018 @VisibleForTesting
Nathan Harolda2523312018-01-05 19:25:13 -08001019 void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException {
1020 IpSecAlgorithm auth = config.getAuthentication();
1021 IpSecAlgorithm crypt = config.getEncryption();
1022 IpSecAlgorithm aead = config.getAuthenticatedEncryption();
Benedict Wong4f255702017-11-06 20:49:10 -08001023
Nathan Harolda2523312018-01-05 19:25:13 -08001024 // Validate the algorithm set
1025 Preconditions.checkArgument(
1026 aead != null || crypt != null || auth != null,
1027 "No Encryption or Authentication algorithms specified");
1028 Preconditions.checkArgument(
1029 auth == null || auth.isAuthentication(),
1030 "Unsupported algorithm for Authentication");
1031 Preconditions.checkArgument(
Benedict Wong4f255702017-11-06 20:49:10 -08001032 crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption");
Nathan Harolda2523312018-01-05 19:25:13 -08001033 Preconditions.checkArgument(
1034 aead == null || aead.isAead(),
1035 "Unsupported algorithm for Authenticated Encryption");
1036 Preconditions.checkArgument(
1037 aead == null || (auth == null && crypt == null),
1038 "Authenticated Encryption is mutually exclusive with other Authentication "
1039 + "or Encryption algorithms");
Benedict Wong4f255702017-11-06 20:49:10 -08001040 }
1041
Nathan Harold93962f32017-03-07 13:23:36 -08001042 /**
Nathan Harolda10003d2017-08-23 13:46:33 -07001043 * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
1044 * IllegalArgumentException if they are not.
1045 */
1046 private void checkIpSecConfig(IpSecConfig config) {
Benedict Wong344bd622017-11-16 15:27:22 -08001047 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1048
Nathan Harolda10003d2017-08-23 13:46:33 -07001049 switch (config.getEncapType()) {
1050 case IpSecTransform.ENCAP_NONE:
1051 break;
1052 case IpSecTransform.ENCAP_ESPINUDP:
1053 case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
Benedict Wong344bd622017-11-16 15:27:22 -08001054 // Retrieve encap socket record; will throw IllegalArgumentException if not found
1055 userRecord.mEncapSocketRecords.getResourceOrThrow(
1056 config.getEncapSocketResourceId());
Nathan Harolda10003d2017-08-23 13:46:33 -07001057
1058 int port = config.getEncapRemotePort();
1059 if (port <= 0 || port > 0xFFFF) {
1060 throw new IllegalArgumentException("Invalid remote UDP port: " + port);
1061 }
1062 break;
1063 default:
1064 throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
1065 }
1066
Nathan Harolda2523312018-01-05 19:25:13 -08001067 validateAlgorithms(config);
Nathan Harolda10003d2017-08-23 13:46:33 -07001068
Nathan Harolda2523312018-01-05 19:25:13 -08001069 // Retrieve SPI record; will throw IllegalArgumentException if not found
1070 SpiRecord s = userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId());
1071
1072 // If no remote address is supplied, then use one from the SPI.
1073 if (TextUtils.isEmpty(config.getDestinationAddress())) {
1074 config.setDestinationAddress(s.getDestinationAddress());
1075 }
1076
1077 // All remote addresses must match
1078 if (!config.getDestinationAddress().equals(s.getDestinationAddress())) {
1079 throw new IllegalArgumentException("Mismatched remote addresseses.");
1080 }
1081
1082 // This check is technically redundant due to the chain of custody between the SPI and
1083 // the IpSecConfig, but in the future if the dest is allowed to be set explicitly in
1084 // the transform, this will prevent us from messing up.
1085 checkInetAddress(config.getDestinationAddress());
1086
1087 // Require a valid source address for all transforms.
1088 checkInetAddress(config.getSourceAddress());
1089
1090 switch (config.getMode()) {
1091 case IpSecTransform.MODE_TRANSPORT:
1092 case IpSecTransform.MODE_TUNNEL:
1093 break;
1094 default:
1095 throw new IllegalArgumentException(
1096 "Invalid IpSecTransform.mode: " + config.getMode());
Nathan Harolda10003d2017-08-23 13:46:33 -07001097 }
1098 }
1099
1100 /**
Nathan Harold93962f32017-03-07 13:23:36 -08001101 * Create a transport mode transform, which represent two security associations (one in each
1102 * direction) in the kernel. The transform will be cached by the system server and must be freed
1103 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
1104 * that are using it, which will result in all of those sockets becoming unable to send or
1105 * receive data.
1106 */
1107 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001108 public synchronized IpSecTransformResponse createTransportModeTransform(
1109 IpSecConfig c, IBinder binder) throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -07001110 checkIpSecConfig(c);
1111 checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
Nathan Haroldd8c74292017-12-13 19:16:33 -08001112 final int resourceId = mNextResourceId++;
Benedict Wong344bd622017-11-16 15:27:22 -08001113
1114 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1115
Nathan Harolda2523312018-01-05 19:25:13 -08001116 // Avoid resizing by creating a dependency array of min-size 2 (1 UDP encap + 1 SPI)
1117 List<RefcountedResource> dependencies = new ArrayList<>(2);
Benedict Wong344bd622017-11-16 15:27:22 -08001118
1119 if (!userRecord.mTransformQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -07001120 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
1121 }
Nathan Harolda10003d2017-08-23 13:46:33 -07001122
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001123 int encapType, encapLocalPort = 0, encapRemotePort = 0;
Benedict Wong344bd622017-11-16 15:27:22 -08001124 EncapSocketRecord socketRecord = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001125 encapType = c.getEncapType();
1126 if (encapType != IpSecTransform.ENCAP_NONE) {
Benedict Wong344bd622017-11-16 15:27:22 -08001127 RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
1128 userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
1129 c.getEncapSocketResourceId());
1130 dependencies.add(refcountedSocketRecord);
1131
1132 socketRecord = refcountedSocketRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001133 encapLocalPort = socketRecord.getPort();
1134 encapRemotePort = c.getEncapRemotePort();
1135 }
1136
Nathan Harolda2523312018-01-05 19:25:13 -08001137 IpSecAlgorithm auth = c.getAuthentication();
1138 IpSecAlgorithm crypt = c.getEncryption();
1139 IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001140
Nathan Harolda2523312018-01-05 19:25:13 -08001141 RefcountedResource<SpiRecord> refcountedSpiRecord =
1142 userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId());
1143 dependencies.add(refcountedSpiRecord);
1144 SpiRecord spiRecord = refcountedSpiRecord.getResource();
Benedict Wong344bd622017-11-16 15:27:22 -08001145
Nathan Harolda2523312018-01-05 19:25:13 -08001146 try {
1147 mSrvConfig
1148 .getNetdInstance()
1149 .ipSecAddSecurityAssociation(
1150 resourceId,
1151 c.getMode(),
1152 c.getSourceAddress(),
1153 c.getDestinationAddress(),
1154 (c.getNetwork() != null) ? c.getNetwork().netId : 0,
1155 spiRecord.getSpi(),
1156 (auth != null) ? auth.getName() : "",
1157 (auth != null) ? auth.getKey() : new byte[] {},
1158 (auth != null) ? auth.getTruncationLengthBits() : 0,
1159 (crypt != null) ? crypt.getName() : "",
1160 (crypt != null) ? crypt.getKey() : new byte[] {},
1161 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
1162 (authCrypt != null) ? authCrypt.getName() : "",
1163 (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
1164 (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
1165 encapType,
1166 encapLocalPort,
1167 encapRemotePort);
1168 } catch (ServiceSpecificException e) {
1169 // FIXME: get the error code and throw is at an IOException from Errno Exception
1170 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001171 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001172 // Both SAs were created successfully, time to construct a record and lock it away
Benedict Wong344bd622017-11-16 15:27:22 -08001173 userRecord.mTransformRecords.put(
1174 resourceId,
1175 new RefcountedResource<TransformRecord>(
Nathan Harolda2523312018-01-05 19:25:13 -08001176 new TransformRecord(resourceId, c, spiRecord, socketRecord),
Benedict Wong344bd622017-11-16 15:27:22 -08001177 binder,
1178 dependencies.toArray(new RefcountedResource[dependencies.size()])));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001179 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001180 }
1181
1182 /**
1183 * Delete a transport mode transform that was previously allocated by + registered with the
1184 * system server. If this is called on an inactive (or non-existent) transform, it will not
1185 * return an error. It's safe to de-allocate transforms that may have already been deleted for
1186 * other reasons.
1187 */
1188 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001189 public synchronized void deleteTransportModeTransform(int resourceId) throws RemoteException {
1190 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1191 releaseResource(userRecord.mTransformRecords, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001192 }
1193
1194 /**
1195 * Apply an active transport mode transform to a socket, which will apply the IPsec security
1196 * association as a correspondent policy to the provided socket
1197 */
1198 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001199 public synchronized void applyTransportModeTransform(
Nathan Harolda2523312018-01-05 19:25:13 -08001200 ParcelFileDescriptor socket, int direction, int resourceId) throws RemoteException {
Benedict Wong344bd622017-11-16 15:27:22 -08001201 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harolda2523312018-01-05 19:25:13 -08001202 checkDirection(direction);
Benedict Wong344bd622017-11-16 15:27:22 -08001203 // Get transform record; if no transform is found, will throw IllegalArgumentException
1204 TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001205
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001206 // TODO: make this a function.
1207 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
1208 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
1209 }
1210
1211 IpSecConfig c = info.getConfig();
1212 try {
Nathan Harolda2523312018-01-05 19:25:13 -08001213 mSrvConfig
1214 .getNetdInstance()
1215 .ipSecApplyTransportModeTransform(
1216 socket.getFileDescriptor(),
1217 resourceId,
1218 direction,
1219 c.getSourceAddress(),
1220 c.getDestinationAddress(),
1221 info.getSpiRecord().getSpi());
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001222 } catch (ServiceSpecificException e) {
manojboopathic4be79d2017-11-30 17:11:49 -08001223 if (e.errorCode == EINVAL) {
1224 throw new IllegalArgumentException(e.toString());
1225 } else {
1226 throw e;
1227 }
Nathan Harold93962f32017-03-07 13:23:36 -08001228 }
1229 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001230
Nathan Harold93962f32017-03-07 13:23:36 -08001231 /**
Nathan Harolda2523312018-01-05 19:25:13 -08001232 * Remove transport mode transforms from a socket, applying the default (empty) policy. This
1233 * ensures that NO IPsec policy is applied to the socket (would be the equivalent of applying a
1234 * policy that performs no IPsec). Today the resourceId parameter is passed but not used:
1235 * reserved for future improved input validation.
Nathan Harold93962f32017-03-07 13:23:36 -08001236 */
1237 @Override
Nathan Harolda2523312018-01-05 19:25:13 -08001238 public synchronized void removeTransportModeTransforms(
1239 ParcelFileDescriptor socket, int resourceId) throws RemoteException {
Nathan Harold93962f32017-03-07 13:23:36 -08001240 try {
ludi1a06aa72017-05-12 09:15:00 -07001241 mSrvConfig
1242 .getNetdInstance()
1243 .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
Nathan Harold93962f32017-03-07 13:23:36 -08001244 } catch (ServiceSpecificException e) {
1245 // FIXME: get the error code and throw is at an IOException from Errno Exception
1246 }
1247 }
1248
1249 @Override
ludib0c95b12017-05-22 10:52:23 -07001250 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Nathan Harold1afbef42017-03-01 18:55:06 -08001251 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
ludib0c95b12017-05-22 10:52:23 -07001252
1253 pw.println("IpSecService dump:");
Nathan Harold1afbef42017-03-01 18:55:06 -08001254 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
1255 pw.println();
ludib0c95b12017-05-22 10:52:23 -07001256
Benedict Wong344bd622017-11-16 15:27:22 -08001257 pw.println("mUserResourceTracker:");
1258 pw.println(mUserResourceTracker);
Nathan Harold1afbef42017-03-01 18:55:06 -08001259 }
1260}