blob: 02cfe3dc75e5ca0eaaba254e86e72d9901a5e5f2 [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;
28import android.net.IIpSecService;
29import android.net.INetd;
Nathan Harold93962f32017-03-07 13:23:36 -080030import android.net.IpSecAlgorithm;
31import android.net.IpSecConfig;
32import android.net.IpSecManager;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070033import android.net.IpSecSpiResponse;
Nathan Harold93962f32017-03-07 13:23:36 -080034import android.net.IpSecTransform;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070035import android.net.IpSecTransformResponse;
36import android.net.IpSecUdpEncapResponse;
Nathan Harolda10003d2017-08-23 13:46:33 -070037import android.net.NetworkUtils;
Benedict Wongbabe5d72017-12-03 19:42:36 -080038import android.net.TrafficStats;
Nathan Harold1afbef42017-03-01 18:55:06 -080039import android.net.util.NetdService;
Nathan Harold93962f32017-03-07 13:23:36 -080040import android.os.Binder;
Nathan Harold93962f32017-03-07 13:23:36 -080041import android.os.IBinder;
42import android.os.ParcelFileDescriptor;
Nathan Harold1afbef42017-03-01 18:55:06 -080043import android.os.RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -080044import android.os.ServiceSpecificException;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070045import android.system.ErrnoException;
46import android.system.Os;
47import android.system.OsConstants;
Nathan Harolda10003d2017-08-23 13:46:33 -070048import android.text.TextUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080049import android.util.Log;
50import android.util.Slog;
Nathan Harold93962f32017-03-07 13:23:36 -080051import android.util.SparseArray;
Nathan Harolda10003d2017-08-23 13:46:33 -070052
Nathan Harold93962f32017-03-07 13:23:36 -080053import com.android.internal.annotations.GuardedBy;
ludi1a06aa72017-05-12 09:15:00 -070054import com.android.internal.annotations.VisibleForTesting;
Benedict Wong4f255702017-11-06 20:49:10 -080055import com.android.internal.util.Preconditions;
Nathan Harolda10003d2017-08-23 13:46:33 -070056
Nathan Harold1afbef42017-03-01 18:55:06 -080057import java.io.FileDescriptor;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070058import java.io.IOException;
Nathan Harold1afbef42017-03-01 18:55:06 -080059import java.io.PrintWriter;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070060import java.net.InetAddress;
61import java.net.InetSocketAddress;
62import java.net.UnknownHostException;
Benedict Wong409c8ca2017-10-26 19:41:43 -070063import java.util.ArrayList;
64import java.util.List;
Nathan Harold93962f32017-03-07 13:23:36 -080065import java.util.concurrent.atomic.AtomicInteger;
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 =
86 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.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 /**
107 * The next non-repeating global ID for tracking resources between users, this service,
108 * and kernel data structures. Accessing this variable is not thread safe, so it is
109 * only read or modified within blocks synchronized on IpSecService.this. We want to
110 * avoid -1 (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
111 */
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;
539 private final SpiRecord[] mSpis;
Benedict Wong344bd622017-11-16 15:27:22 -0800540 private final EncapSocketRecord mSocket;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700541
542 TransformRecord(
Benedict Wong344bd622017-11-16 15:27:22 -0800543 int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) {
544 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800545 mConfig = config;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700546 mSpis = spis;
547 mSocket = socket;
Nathan Harold93962f32017-03-07 13:23:36 -0800548 }
549
550 public IpSecConfig getConfig() {
551 return mConfig;
552 }
553
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700554 public SpiRecord getSpiRecord(int direction) {
555 return mSpis[direction];
556 }
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 Harold93962f32017-03-07 13:23:36 -0800561 for (int direction : DIRECTIONS) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700562 int spi = mSpis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800563 try {
ludi1a06aa72017-05-12 09:15:00 -0700564 mSrvConfig
565 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800566 .ipSecDeleteSecurityAssociation(
567 mResourceId,
568 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -0700569 mConfig.getLocalAddress(),
570 mConfig.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700571 spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800572 } catch (ServiceSpecificException e) {
573 // FIXME: get the error code and throw is at an IOException from Errno Exception
574 } catch (RemoteException e) {
575 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
576 }
577 }
Nathan Harold93962f32017-03-07 13:23:36 -0800578
Benedict Wong344bd622017-11-16 15:27:22 -0800579 getResourceTracker().give();
Nathan Harold93962f32017-03-07 13:23:36 -0800580 }
ludib0c95b12017-05-22 10:52:23 -0700581
Benedict Wong344bd622017-11-16 15:27:22 -0800582 @Override
583 public void invalidate() throws RemoteException {
584 getUserRecord().removeTransformRecord(mResourceId);
585 }
586
587 @Override
Nathan Harolda1afbd82017-04-24 16:16:34 -0700588 protected ResourceTracker getResourceTracker() {
Benedict Wong344bd622017-11-16 15:27:22 -0800589 return getUserRecord().mTransformQuotaTracker;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700590 }
591
ludib0c95b12017-05-22 10:52:23 -0700592 @Override
593 public String toString() {
594 StringBuilder strBuilder = new StringBuilder();
595 strBuilder
596 .append("{super=")
597 .append(super.toString())
598 .append(", mSocket=")
599 .append(mSocket)
600 .append(", mSpis[OUT].mResourceId=")
601 .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId)
602 .append(", mSpis[IN].mResourceId=")
603 .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId)
604 .append(", mConfig=")
605 .append(mConfig)
606 .append("}");
607 return strBuilder.toString();
608 }
Nathan Harold93962f32017-03-07 13:23:36 -0800609 }
610
Benedict Wong344bd622017-11-16 15:27:22 -0800611 private final class SpiRecord extends KernelResourceRecord {
Nathan Harold93962f32017-03-07 13:23:36 -0800612 private final int mDirection;
613 private final String mLocalAddress;
614 private final String mRemoteAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800615 private int mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700616
617 private boolean mOwnedByTransform = false;
Nathan Harold93962f32017-03-07 13:23:36 -0800618
619 SpiRecord(
620 int resourceId,
621 int direction,
622 String localAddress,
623 String remoteAddress,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700624 int spi) {
Benedict Wong344bd622017-11-16 15:27:22 -0800625 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800626 mDirection = direction;
627 mLocalAddress = localAddress;
628 mRemoteAddress = remoteAddress;
629 mSpi = spi;
Nathan Harold93962f32017-03-07 13:23:36 -0800630 }
631
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700632 /** always guarded by IpSecService#this */
633 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800634 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700635 if (mOwnedByTransform) {
636 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
637 // Because SPIs are "handed off" to transform, objects, they should never be
638 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
639 // thus ownership and responsibility for freeing these resources passes to the
640 // Transform object). Thus, we should let the user free them without penalty once
641 // they are applied in a Transform object.
642 return;
643 }
644
Nathan Harold93962f32017-03-07 13:23:36 -0800645 try {
ludi1a06aa72017-05-12 09:15:00 -0700646 mSrvConfig
647 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800648 .ipSecDeleteSecurityAssociation(
649 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
650 } catch (ServiceSpecificException e) {
651 // FIXME: get the error code and throw is at an IOException from Errno Exception
652 } catch (RemoteException e) {
653 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
654 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700655
656 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800657
Benedict Wong344bd622017-11-16 15:27:22 -0800658 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700659 }
660
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700661 public int getSpi() {
662 return mSpi;
663 }
664
665 public void setOwnedByTransform() {
666 if (mOwnedByTransform) {
667 // Programming error
Andreas Gamped6d8e452017-07-11 10:25:09 -0700668 throw new IllegalStateException("Cannot own an SPI twice!");
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700669 }
670
671 mOwnedByTransform = true;
Nathan Harold93962f32017-03-07 13:23:36 -0800672 }
ludib0c95b12017-05-22 10:52:23 -0700673
674 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800675 public void invalidate() throws RemoteException {
676 getUserRecord().removeSpiRecord(mResourceId);
677 }
678
679 @Override
680 protected ResourceTracker getResourceTracker() {
681 return getUserRecord().mSpiQuotaTracker;
682 }
683
684 @Override
ludib0c95b12017-05-22 10:52:23 -0700685 public String toString() {
686 StringBuilder strBuilder = new StringBuilder();
687 strBuilder
688 .append("{super=")
689 .append(super.toString())
690 .append(", mSpi=")
691 .append(mSpi)
692 .append(", mDirection=")
693 .append(mDirection)
694 .append(", mLocalAddress=")
695 .append(mLocalAddress)
696 .append(", mRemoteAddress=")
697 .append(mRemoteAddress)
698 .append(", mOwnedByTransform=")
699 .append(mOwnedByTransform)
700 .append("}");
701 return strBuilder.toString();
702 }
Nathan Harold93962f32017-03-07 13:23:36 -0800703 }
704
Benedict Wong344bd622017-11-16 15:27:22 -0800705 private final class EncapSocketRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700706 private FileDescriptor mSocket;
707 private final int mPort;
Nathan Harold93962f32017-03-07 13:23:36 -0800708
Benedict Wong344bd622017-11-16 15:27:22 -0800709 EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
710 super(resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700711 mSocket = socket;
712 mPort = port;
713 }
714
715 /** always guarded by IpSecService#this */
716 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800717 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700718 Log.d(TAG, "Closing port " + mPort);
719 IoUtils.closeQuietly(mSocket);
720 mSocket = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700721
Benedict Wong344bd622017-11-16 15:27:22 -0800722 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700723 }
724
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700725 public int getPort() {
726 return mPort;
727 }
728
729 public FileDescriptor getSocket() {
730 return mSocket;
731 }
ludib0c95b12017-05-22 10:52:23 -0700732
733 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800734 protected ResourceTracker getResourceTracker() {
735 return getUserRecord().mSocketQuotaTracker;
736 }
737
738 @Override
739 public void invalidate() {
740 getUserRecord().removeEncapSocketRecord(mResourceId);
741 }
742
743 @Override
ludib0c95b12017-05-22 10:52:23 -0700744 public String toString() {
745 return new StringBuilder()
746 .append("{super=")
747 .append(super.toString())
748 .append(", mSocket=")
749 .append(mSocket)
750 .append(", mPort=")
751 .append(mPort)
752 .append("}")
753 .toString();
754 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700755 }
Nathan Harold93962f32017-03-07 13:23:36 -0800756
Nathan Harold1afbef42017-03-01 18:55:06 -0800757 /**
758 * Constructs a new IpSecService instance
759 *
760 * @param context Binder context for this service
761 */
762 private IpSecService(Context context) {
ludi1a06aa72017-05-12 09:15:00 -0700763 this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
Nathan Harold1afbef42017-03-01 18:55:06 -0800764 }
765
766 static IpSecService create(Context context) throws InterruptedException {
767 final IpSecService service = new IpSecService(context);
768 service.connectNativeNetdService();
769 return service;
770 }
771
ludi1a06aa72017-05-12 09:15:00 -0700772 /** @hide */
773 @VisibleForTesting
774 public IpSecService(Context context, IpSecServiceConfiguration config) {
Benedict Wongbabe5d72017-12-03 19:42:36 -0800775 this(context, config, (fd, uid) -> {
776 try{
777 TrafficStats.setThreadStatsUid(uid);
778 TrafficStats.tagFileDescriptor(fd);
779 } finally {
780 TrafficStats.clearThreadStatsUid();
781 }
782 });
783 }
784
785 /** @hide */
786 @VisibleForTesting
787 public IpSecService(
788 Context context, IpSecServiceConfiguration config, UidFdTagger uidFdTagger) {
ludi1a06aa72017-05-12 09:15:00 -0700789 mContext = context;
790 mSrvConfig = config;
Benedict Wongbabe5d72017-12-03 19:42:36 -0800791 mUidFdTagger = uidFdTagger;
ludi1a06aa72017-05-12 09:15:00 -0700792 }
793
Nathan Harold1afbef42017-03-01 18:55:06 -0800794 public void systemReady() {
795 if (isNetdAlive()) {
796 Slog.d(TAG, "IpSecService is ready");
797 } else {
798 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
799 }
800 }
801
802 private void connectNativeNetdService() {
803 // Avoid blocking the system server to do this
Nathan Haroldb0e05082017-07-17 14:01:53 -0700804 new Thread() {
805 @Override
806 public void run() {
807 synchronized (IpSecService.this) {
ludi1a06aa72017-05-12 09:15:00 -0700808 NetdService.get(NETD_FETCH_TIMEOUT_MS);
Nathan Haroldb0e05082017-07-17 14:01:53 -0700809 }
810 }
811 }.start();
Nathan Harold1afbef42017-03-01 18:55:06 -0800812 }
813
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700814 synchronized boolean isNetdAlive() {
815 try {
ludi1a06aa72017-05-12 09:15:00 -0700816 final INetd netd = mSrvConfig.getNetdInstance();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700817 if (netd == null) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800818 return false;
819 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700820 return netd.isAlive();
821 } catch (RemoteException re) {
822 return false;
Nathan Harold1afbef42017-03-01 18:55:06 -0800823 }
824 }
825
Nathan Harolda10003d2017-08-23 13:46:33 -0700826 /**
827 * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
828 * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
829 */
830 private static void checkInetAddress(String inetAddress) {
831 if (TextUtils.isEmpty(inetAddress)) {
832 throw new IllegalArgumentException("Unspecified address");
833 }
834
835 InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
836
837 if (checkAddr.isAnyLocalAddress()) {
838 throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
839 }
840 }
841
842 /**
843 * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
844 * DIRECTION_IN or DIRECTION_OUT
845 */
846 private static void checkDirection(int direction) {
847 switch (direction) {
848 case IpSecTransform.DIRECTION_OUT:
849 case IpSecTransform.DIRECTION_IN:
850 return;
851 }
852 throw new IllegalArgumentException("Invalid Direction: " + direction);
853 }
854
Nathan Harold93962f32017-03-07 13:23:36 -0800855 /** Get a new SPI and maintain the reservation in the system server */
Jonathan Basseri5fb92902017-11-16 10:58:01 -0800856 @Override
857 public synchronized IpSecSpiResponse allocateSecurityParameterIndex(
Nathan Harold93962f32017-03-07 13:23:36 -0800858 int direction, String remoteAddress, int requestedSpi, IBinder binder)
859 throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -0700860 checkDirection(direction);
861 checkInetAddress(remoteAddress);
862 /* requestedSpi can be anything in the int range, so no check is needed. */
Jonathan Basseri5fb92902017-11-16 10:58:01 -0800863 checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
Nathan Harolda10003d2017-08-23 13:46:33 -0700864
Benedict Wong344bd622017-11-16 15:27:22 -0800865 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Haroldd8c74292017-12-13 19:16:33 -0800866 final int resourceId = mNextResourceId++;
Nathan Harold93962f32017-03-07 13:23:36 -0800867
868 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
869 String localAddress = "";
Nathan Harolda1afbd82017-04-24 16:16:34 -0700870
Nathan Harold93962f32017-03-07 13:23:36 -0800871 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800872 if (!userRecord.mSpiQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700873 return new IpSecSpiResponse(
Nathan Harolda10003d2017-08-23 13:46:33 -0700874 IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700875 }
Nathan Harold93962f32017-03-07 13:23:36 -0800876 spi =
ludi1a06aa72017-05-12 09:15:00 -0700877 mSrvConfig
878 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800879 .ipSecAllocateSpi(
880 resourceId,
881 direction,
882 localAddress,
883 remoteAddress,
884 requestedSpi);
885 Log.d(TAG, "Allocated SPI " + spi);
Benedict Wong344bd622017-11-16 15:27:22 -0800886 userRecord.mSpiRecords.put(
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700887 resourceId,
Benedict Wong344bd622017-11-16 15:27:22 -0800888 new RefcountedResource<SpiRecord>(
889 new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi),
890 binder));
Nathan Harold93962f32017-03-07 13:23:36 -0800891 } catch (ServiceSpecificException e) {
892 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700893 return new IpSecSpiResponse(
Nathan Harolda1afbd82017-04-24 16:16:34 -0700894 IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800895 } catch (RemoteException e) {
896 throw e.rethrowFromSystemServer();
897 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700898 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
899 }
900
901 /* This method should only be called from Binder threads. Do not call this from
902 * within the system server as it will crash the system on failure.
903 */
Benedict Wong344bd622017-11-16 15:27:22 -0800904 private void releaseResource(RefcountedResourceArray resArray, int resourceId)
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700905 throws RemoteException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700906
Benedict Wong344bd622017-11-16 15:27:22 -0800907 resArray.getRefcountedResourceOrThrow(resourceId).userRelease();
Nathan Harold93962f32017-03-07 13:23:36 -0800908 }
909
910 /** Release a previously allocated SPI that has been registered with the system server */
911 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800912 public synchronized void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
913 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
914 releaseResource(userRecord.mSpiRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700915 }
916
917 /**
918 * This function finds and forcibly binds to a random system port, ensuring that the port cannot
919 * be unbound.
920 *
921 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
922 * a random open port and then bind by number, this function creates a temp socket, binds to a
923 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
924 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
925 * FileHandle.
926 *
927 * <p>The loop in this function handles the inherent race window between un-binding to a port
928 * and re-binding, during which the system could *technically* hand that port out to someone
929 * else.
930 */
Benedict Wongf186d672017-10-10 20:44:28 -0700931 private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700932 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
933 try {
934 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
935 Os.bind(probeSocket, INADDR_ANY, 0);
936 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
937 Os.close(probeSocket);
938 Log.v(TAG, "Binding to port " + port);
939 Os.bind(sockFd, INADDR_ANY, port);
Benedict Wongf186d672017-10-10 20:44:28 -0700940 return port;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700941 } catch (ErrnoException e) {
942 // Someone miraculously claimed the port just after we closed probeSocket.
943 if (e.errno == OsConstants.EADDRINUSE) {
944 continue;
945 }
946 throw e.rethrowAsIOException();
947 }
948 }
949 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
950 }
Nathan Harold93962f32017-03-07 13:23:36 -0800951
952 /**
Benedict Wongbabe5d72017-12-03 19:42:36 -0800953 * Functional interface to do traffic tagging of given sockets to UIDs.
954 *
955 * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
956 * sockets are billed to the UID that the UDP encap socket was created on behalf of.
957 *
958 * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
959 * methods that cannot be easily mocked/tested.
960 */
961 @VisibleForTesting
962 public interface UidFdTagger {
963 /**
964 * Sets socket tag to assign all traffic to the provided UID.
965 *
966 * <p>Since the socket is created on behalf of an unprivileged application, all traffic
967 * should be accounted to the UID of the unprivileged application.
968 */
969 public void tag(FileDescriptor fd, int uid) throws IOException;
970 }
971
972 /**
Nathan Harold93962f32017-03-07 13:23:36 -0800973 * Open a socket via the system server and bind it to the specified port (random if port=0).
974 * This will return a PFD to the user that represent a bound UDP socket. The system server will
975 * cache the socket and a record of its owner so that it can and must be freed when no longer
976 * needed.
977 */
978 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700979 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
980 throws RemoteException {
981 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
982 throw new IllegalArgumentException(
983 "Specified port number must be a valid non-reserved UDP port");
984 }
Nathan Harolda10003d2017-08-23 13:46:33 -0700985 checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
986
Benedict Wongbabe5d72017-12-03 19:42:36 -0800987 int callingUid = Binder.getCallingUid();
988 UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
Nathan Haroldd8c74292017-12-13 19:16:33 -0800989 final int resourceId = mNextResourceId++;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700990 FileDescriptor sockFd = null;
991 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800992 if (!userRecord.mSocketQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700993 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
994 }
995
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700996 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Benedict Wongbabe5d72017-12-03 19:42:36 -0800997 mUidFdTagger.tag(sockFd, callingUid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700998
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700999 // This code is common to both the unspecified and specified port cases
1000 Os.setsockoptInt(
1001 sockFd,
1002 OsConstants.IPPROTO_UDP,
1003 OsConstants.UDP_ENCAP,
1004 OsConstants.UDP_ENCAP_ESPINUDP);
1005
Benedict Wongba8d3132017-12-06 21:56:35 -08001006 mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
1007 if (port != 0) {
1008 Log.v(TAG, "Binding to port " + port);
1009 Os.bind(sockFd, INADDR_ANY, port);
1010 } else {
1011 port = bindToRandomPort(sockFd);
1012 }
1013
Benedict Wong344bd622017-11-16 15:27:22 -08001014 userRecord.mEncapSocketRecords.put(
1015 resourceId,
1016 new RefcountedResource<EncapSocketRecord>(
1017 new EncapSocketRecord(resourceId, sockFd, port), binder));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001018 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
1019 } catch (IOException | ErrnoException e) {
1020 IoUtils.closeQuietly(sockFd);
1021 }
1022 // If we make it to here, then something has gone wrong and we couldn't open a socket.
1023 // The only reasonable condition that would cause that is resource unavailable.
1024 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001025 }
1026
1027 /** close a socket that has been been allocated by and registered with the system server */
1028 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001029 public synchronized void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
1030 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1031 releaseResource(userRecord.mEncapSocketRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001032 }
Nathan Harold93962f32017-03-07 13:23:36 -08001033
Benedict Wong4f255702017-11-06 20:49:10 -08001034 @VisibleForTesting
1035 void validateAlgorithms(IpSecConfig config, int direction) throws IllegalArgumentException {
1036 IpSecAlgorithm auth = config.getAuthentication(direction);
1037 IpSecAlgorithm crypt = config.getEncryption(direction);
1038 IpSecAlgorithm aead = config.getAuthenticatedEncryption(direction);
1039
1040 // Validate the algorithm set
1041 Preconditions.checkArgument(
1042 aead != null || crypt != null || auth != null,
1043 "No Encryption or Authentication algorithms specified");
1044 Preconditions.checkArgument(
1045 auth == null || auth.isAuthentication(),
1046 "Unsupported algorithm for Authentication");
1047 Preconditions.checkArgument(
1048 crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption");
1049 Preconditions.checkArgument(
1050 aead == null || aead.isAead(),
1051 "Unsupported algorithm for Authenticated Encryption");
1052 Preconditions.checkArgument(
1053 aead == null || (auth == null && crypt == null),
1054 "Authenticated Encryption is mutually exclusive with other Authentication "
1055 + "or Encryption algorithms");
1056 }
1057
Nathan Harold93962f32017-03-07 13:23:36 -08001058 /**
Nathan Harolda10003d2017-08-23 13:46:33 -07001059 * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
1060 * IllegalArgumentException if they are not.
1061 */
1062 private void checkIpSecConfig(IpSecConfig config) {
Benedict Wong344bd622017-11-16 15:27:22 -08001063 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1064
Nathan Harolda10003d2017-08-23 13:46:33 -07001065 if (config.getLocalAddress() == null) {
1066 throw new IllegalArgumentException("Invalid null Local InetAddress");
1067 }
1068
1069 if (config.getRemoteAddress() == null) {
1070 throw new IllegalArgumentException("Invalid null Remote InetAddress");
1071 }
1072
1073 switch (config.getMode()) {
1074 case IpSecTransform.MODE_TRANSPORT:
1075 if (!config.getLocalAddress().isEmpty()) {
1076 throw new IllegalArgumentException("Non-empty Local Address");
1077 }
1078 // Must be valid, and not a wildcard
1079 checkInetAddress(config.getRemoteAddress());
1080 break;
1081 case IpSecTransform.MODE_TUNNEL:
1082 break;
1083 default:
1084 throw new IllegalArgumentException(
1085 "Invalid IpSecTransform.mode: " + config.getMode());
1086 }
1087
1088 switch (config.getEncapType()) {
1089 case IpSecTransform.ENCAP_NONE:
1090 break;
1091 case IpSecTransform.ENCAP_ESPINUDP:
1092 case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
Benedict Wong344bd622017-11-16 15:27:22 -08001093 // Retrieve encap socket record; will throw IllegalArgumentException if not found
1094 userRecord.mEncapSocketRecords.getResourceOrThrow(
1095 config.getEncapSocketResourceId());
Nathan Harolda10003d2017-08-23 13:46:33 -07001096
1097 int port = config.getEncapRemotePort();
1098 if (port <= 0 || port > 0xFFFF) {
1099 throw new IllegalArgumentException("Invalid remote UDP port: " + port);
1100 }
1101 break;
1102 default:
1103 throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
1104 }
1105
1106 for (int direction : DIRECTIONS) {
Benedict Wong4f255702017-11-06 20:49:10 -08001107 validateAlgorithms(config, direction);
Nathan Harolda10003d2017-08-23 13:46:33 -07001108
Benedict Wong344bd622017-11-16 15:27:22 -08001109 // Retrieve SPI record; will throw IllegalArgumentException if not found
1110 userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction));
Nathan Harolda10003d2017-08-23 13:46:33 -07001111 }
1112 }
1113
1114 /**
Nathan Harold93962f32017-03-07 13:23:36 -08001115 * Create a transport mode transform, which represent two security associations (one in each
1116 * direction) in the kernel. The transform will be cached by the system server and must be freed
1117 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
1118 * that are using it, which will result in all of those sockets becoming unable to send or
1119 * receive data.
1120 */
1121 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001122 public synchronized IpSecTransformResponse createTransportModeTransform(
1123 IpSecConfig c, IBinder binder) throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -07001124 checkIpSecConfig(c);
1125 checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
Nathan Haroldd8c74292017-12-13 19:16:33 -08001126 final int resourceId = mNextResourceId++;
Benedict Wong344bd622017-11-16 15:27:22 -08001127
1128 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1129
1130 // Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs)
1131 List<RefcountedResource> dependencies = new ArrayList<>(3);
1132
1133 if (!userRecord.mTransformQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -07001134 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
1135 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001136 SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
Nathan Harolda10003d2017-08-23 13:46:33 -07001137
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001138 int encapType, encapLocalPort = 0, encapRemotePort = 0;
Benedict Wong344bd622017-11-16 15:27:22 -08001139 EncapSocketRecord socketRecord = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001140 encapType = c.getEncapType();
1141 if (encapType != IpSecTransform.ENCAP_NONE) {
Benedict Wong344bd622017-11-16 15:27:22 -08001142 RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
1143 userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
1144 c.getEncapSocketResourceId());
1145 dependencies.add(refcountedSocketRecord);
1146
1147 socketRecord = refcountedSocketRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001148 encapLocalPort = socketRecord.getPort();
1149 encapRemotePort = c.getEncapRemotePort();
1150 }
1151
Nathan Harold93962f32017-03-07 13:23:36 -08001152 for (int direction : DIRECTIONS) {
1153 IpSecAlgorithm auth = c.getAuthentication(direction);
1154 IpSecAlgorithm crypt = c.getEncryption(direction);
Benedict Wong0febe5e2017-08-22 21:42:33 -07001155 IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001156
Benedict Wong344bd622017-11-16 15:27:22 -08001157 RefcountedResource<SpiRecord> refcountedSpiRecord =
1158 userRecord.mSpiRecords.getRefcountedResourceOrThrow(
1159 c.getSpiResourceId(direction));
1160 dependencies.add(refcountedSpiRecord);
1161
1162 spis[direction] = refcountedSpiRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001163 int spi = spis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -08001164 try {
Nathan Harolda10003d2017-08-23 13:46:33 -07001165 mSrvConfig
1166 .getNetdInstance()
ludi0f807892017-05-20 14:15:09 -07001167 .ipSecAddSecurityAssociation(
1168 resourceId,
1169 c.getMode(),
1170 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001171 c.getLocalAddress(),
1172 c.getRemoteAddress(),
1173 (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
ludi0f807892017-05-20 14:15:09 -07001174 spi,
1175 (auth != null) ? auth.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001176 (auth != null) ? auth.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001177 (auth != null) ? auth.getTruncationLengthBits() : 0,
1178 (crypt != null) ? crypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001179 (crypt != null) ? crypt.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001180 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
Benedict Wong0febe5e2017-08-22 21:42:33 -07001181 (authCrypt != null) ? authCrypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001182 (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
Benedict Wong0febe5e2017-08-22 21:42:33 -07001183 (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
ludi0f807892017-05-20 14:15:09 -07001184 encapType,
1185 encapLocalPort,
1186 encapRemotePort);
Nathan Harold93962f32017-03-07 13:23:36 -08001187 } catch (ServiceSpecificException e) {
1188 // FIXME: get the error code and throw is at an IOException from Errno Exception
ludi0f807892017-05-20 14:15:09 -07001189 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001190 }
1191 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001192 // Both SAs were created successfully, time to construct a record and lock it away
Benedict Wong344bd622017-11-16 15:27:22 -08001193 userRecord.mTransformRecords.put(
1194 resourceId,
1195 new RefcountedResource<TransformRecord>(
1196 new TransformRecord(resourceId, c, spis, socketRecord),
1197 binder,
1198 dependencies.toArray(new RefcountedResource[dependencies.size()])));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001199 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001200 }
1201
1202 /**
1203 * Delete a transport mode transform that was previously allocated by + registered with the
1204 * system server. If this is called on an inactive (or non-existent) transform, it will not
1205 * return an error. It's safe to de-allocate transforms that may have already been deleted for
1206 * other reasons.
1207 */
1208 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001209 public synchronized void deleteTransportModeTransform(int resourceId) throws RemoteException {
1210 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1211 releaseResource(userRecord.mTransformRecords, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001212 }
1213
1214 /**
1215 * Apply an active transport mode transform to a socket, which will apply the IPsec security
1216 * association as a correspondent policy to the provided socket
1217 */
1218 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001219 public synchronized void applyTransportModeTransform(
1220 ParcelFileDescriptor socket, int resourceId) throws RemoteException {
Benedict Wong344bd622017-11-16 15:27:22 -08001221 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harold93962f32017-03-07 13:23:36 -08001222
Benedict Wong344bd622017-11-16 15:27:22 -08001223 // Get transform record; if no transform is found, will throw IllegalArgumentException
1224 TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001225
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001226 // TODO: make this a function.
1227 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
1228 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
1229 }
1230
1231 IpSecConfig c = info.getConfig();
1232 try {
1233 for (int direction : DIRECTIONS) {
ludi1a06aa72017-05-12 09:15:00 -07001234 mSrvConfig
1235 .getNetdInstance()
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001236 .ipSecApplyTransportModeTransform(
1237 socket.getFileDescriptor(),
1238 resourceId,
1239 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001240 c.getLocalAddress(),
1241 c.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001242 info.getSpiRecord(direction).getSpi());
Nathan Harold93962f32017-03-07 13:23:36 -08001243 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001244 } catch (ServiceSpecificException e) {
manojboopathic4be79d2017-11-30 17:11:49 -08001245 if (e.errorCode == EINVAL) {
1246 throw new IllegalArgumentException(e.toString());
1247 } else {
1248 throw e;
1249 }
Nathan Harold93962f32017-03-07 13:23:36 -08001250 }
1251 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001252
Nathan Harold93962f32017-03-07 13:23:36 -08001253 /**
1254 * Remove a transport mode transform from a socket, applying the default (empty) policy. This
1255 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
1256 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
1257 * used: reserved for future improved input validation.
1258 */
1259 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001260 public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
Nathan Harold93962f32017-03-07 13:23:36 -08001261 throws RemoteException {
1262 try {
ludi1a06aa72017-05-12 09:15:00 -07001263 mSrvConfig
1264 .getNetdInstance()
1265 .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
Nathan Harold93962f32017-03-07 13:23:36 -08001266 } catch (ServiceSpecificException e) {
1267 // FIXME: get the error code and throw is at an IOException from Errno Exception
1268 }
1269 }
1270
1271 @Override
ludib0c95b12017-05-22 10:52:23 -07001272 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Nathan Harold1afbef42017-03-01 18:55:06 -08001273 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
ludib0c95b12017-05-22 10:52:23 -07001274
1275 pw.println("IpSecService dump:");
Nathan Harold1afbef42017-03-01 18:55:06 -08001276 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
1277 pw.println();
ludib0c95b12017-05-22 10:52:23 -07001278
Benedict Wong344bd622017-11-16 15:27:22 -08001279 pw.println("mUserResourceTracker:");
1280 pw.println(mUserResourceTracker);
Nathan Harold1afbef42017-03-01 18:55:06 -08001281 }
1282}