blob: 232853898dcdd3bc8498db1fd5147649557b07c2 [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;
22import static android.system.OsConstants.IPPROTO_UDP;
23import static android.system.OsConstants.SOCK_DGRAM;
24import static com.android.internal.util.Preconditions.checkNotNull;
Nathan Harold1afbef42017-03-01 18:55:06 -080025
26import android.content.Context;
27import android.net.IIpSecService;
28import android.net.INetd;
Nathan Harold93962f32017-03-07 13:23:36 -080029import android.net.IpSecAlgorithm;
30import android.net.IpSecConfig;
31import android.net.IpSecManager;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070032import android.net.IpSecSpiResponse;
Nathan Harold93962f32017-03-07 13:23:36 -080033import android.net.IpSecTransform;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070034import android.net.IpSecTransformResponse;
35import android.net.IpSecUdpEncapResponse;
Nathan Harolda10003d2017-08-23 13:46:33 -070036import android.net.NetworkUtils;
Benedict Wongbabe5d72017-12-03 19:42:36 -080037import android.net.TrafficStats;
Nathan Harold1afbef42017-03-01 18:55:06 -080038import android.net.util.NetdService;
Nathan Harold93962f32017-03-07 13:23:36 -080039import android.os.Binder;
Nathan Harold93962f32017-03-07 13:23:36 -080040import android.os.IBinder;
41import android.os.ParcelFileDescriptor;
Nathan Harold1afbef42017-03-01 18:55:06 -080042import android.os.RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -080043import android.os.ServiceSpecificException;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070044import android.system.ErrnoException;
45import android.system.Os;
46import android.system.OsConstants;
Nathan Harolda10003d2017-08-23 13:46:33 -070047import android.text.TextUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080048import android.util.Log;
49import android.util.Slog;
Nathan Harold93962f32017-03-07 13:23:36 -080050import android.util.SparseArray;
Nathan Harolda10003d2017-08-23 13:46:33 -070051
Nathan Harold93962f32017-03-07 13:23:36 -080052import com.android.internal.annotations.GuardedBy;
ludi1a06aa72017-05-12 09:15:00 -070053import com.android.internal.annotations.VisibleForTesting;
Benedict Wong4f255702017-11-06 20:49:10 -080054import com.android.internal.util.Preconditions;
Nathan Harolda10003d2017-08-23 13:46:33 -070055
Nathan Harold1afbef42017-03-01 18:55:06 -080056import java.io.FileDescriptor;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070057import java.io.IOException;
Nathan Harold1afbef42017-03-01 18:55:06 -080058import java.io.PrintWriter;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070059import java.net.InetAddress;
60import java.net.InetSocketAddress;
61import java.net.UnknownHostException;
Benedict Wong409c8ca2017-10-26 19:41:43 -070062import java.util.ArrayList;
63import java.util.List;
Nathan Harold93962f32017-03-07 13:23:36 -080064import java.util.concurrent.atomic.AtomicInteger;
Nathan Harolda10003d2017-08-23 13:46:33 -070065
Nathan Harold8dc1fd02017-04-04 19:37:48 -070066import libcore.io.IoUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080067
Benedict Wong409c8ca2017-10-26 19:41:43 -070068/**
69 * A service to manage multiple clients that want to access the IpSec API. The service is
70 * responsible for maintaining a list of clients and managing the resources (and related quotas)
71 * that each of them own.
72 *
73 * <p>Synchronization in IpSecService is done on all entrypoints due to potential race conditions at
74 * the kernel/xfrm level. Further, this allows the simplifying assumption to be made that only one
75 * thread is ever running at a time.
76 *
77 * @hide
78 */
Nathan Harold1afbef42017-03-01 18:55:06 -080079public class IpSecService extends IIpSecService.Stub {
80 private static final String TAG = "IpSecService";
81 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -070082
Nathan Harold1afbef42017-03-01 18:55:06 -080083 private static final String NETD_SERVICE_NAME = "netd";
Nathan Harold93962f32017-03-07 13:23:36 -080084 private static final int[] DIRECTIONS =
85 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
Nathan Harold1afbef42017-03-01 18:55:06 -080086
ludi1a06aa72017-05-12 09:15:00 -070087 private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
Nathan Harold8dc1fd02017-04-04 19:37:48 -070088 private static final int MAX_PORT_BIND_ATTEMPTS = 10;
89 private static final InetAddress INADDR_ANY;
90
91 static {
92 try {
93 INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
94 } catch (UnknownHostException e) {
95 throw new RuntimeException(e);
96 }
97 }
98
99 static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
100 static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
101
102 /* Binder context for this service */
Nathan Harold1afbef42017-03-01 18:55:06 -0800103 private final Context mContext;
104
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700105 /** Should be a never-repeating global ID for resources */
106 private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
Nathan Harold1afbef42017-03-01 18:55:06 -0800107
ludi1a06aa72017-05-12 09:15:00 -0700108 interface IpSecServiceConfiguration {
109 INetd getNetdInstance() throws RemoteException;
110
111 static IpSecServiceConfiguration GETSRVINSTANCE =
112 new IpSecServiceConfiguration() {
113 @Override
114 public INetd getNetdInstance() throws RemoteException {
115 final INetd netd = NetdService.getInstance();
116 if (netd == null) {
117 throw new RemoteException("Failed to Get Netd Instance");
118 }
119 return netd;
120 }
121 };
122 }
123
124 private final IpSecServiceConfiguration mSrvConfig;
Benedict Wongbabe5d72017-12-03 19:42:36 -0800125 final UidFdTagger mUidFdTagger;
ludi1a06aa72017-05-12 09:15:00 -0700126
Benedict Wong409c8ca2017-10-26 19:41:43 -0700127 /**
128 * Interface for user-reference and kernel-resource cleanup.
129 *
130 * <p>This interface must be implemented for a resource to be reference counted.
131 */
132 @VisibleForTesting
133 public interface IResource {
134 /**
135 * Invalidates a IResource object, ensuring it is invalid for the purposes of allocating new
136 * objects dependent on it.
137 *
138 * <p>Implementations of this method are expected to remove references to the IResource
139 * object from the IpSecService's tracking arrays. The removal from the arrays ensures that
140 * the resource is considered invalid for user access or allocation or use in other
141 * resources.
142 *
143 * <p>References to the IResource object may be held by other RefcountedResource objects,
144 * and as such, the kernel resources and quota may not be cleaned up.
145 */
146 void invalidate() throws RemoteException;
147
148 /**
149 * Releases underlying resources and related quotas.
150 *
151 * <p>Implementations of this method are expected to remove all system resources that are
152 * tracked by the IResource object. Due to other RefcountedResource objects potentially
Benedict Wong344bd622017-11-16 15:27:22 -0800153 * having references to the IResource object, freeUnderlyingResources may not always be
Benedict Wong409c8ca2017-10-26 19:41:43 -0700154 * called from releaseIfUnreferencedRecursively().
155 */
156 void freeUnderlyingResources() throws RemoteException;
157 }
158
159 /**
160 * RefcountedResource manages references and dependencies in an exclusively acyclic graph.
161 *
162 * <p>RefcountedResource implements both explicit and implicit resource management. Creating a
163 * RefcountedResource object creates an explicit reference that must be freed by calling
164 * userRelease(). Additionally, adding this object as a child of another RefcountedResource
165 * object will add an implicit reference.
166 *
167 * <p>Resources are cleaned up when all references, both implicit and explicit, are released
168 * (ie, when userRelease() is called and when all parents have called releaseReference() on this
169 * object.)
170 */
171 @VisibleForTesting
172 public class RefcountedResource<T extends IResource> implements IBinder.DeathRecipient {
173 private final T mResource;
174 private final List<RefcountedResource> mChildren;
175 int mRefCount = 1; // starts at 1 for user's reference.
176 IBinder mBinder;
177
178 RefcountedResource(T resource, IBinder binder, RefcountedResource... children) {
179 synchronized (IpSecService.this) {
180 this.mResource = resource;
181 this.mChildren = new ArrayList<>(children.length);
182 this.mBinder = binder;
183
184 for (RefcountedResource child : children) {
185 mChildren.add(child);
186 child.mRefCount++;
187 }
188
189 try {
190 mBinder.linkToDeath(this, 0);
191 } catch (RemoteException e) {
192 binderDied();
193 }
194 }
195 }
196
197 /**
198 * If the Binder object dies, this function is called to free the system resources that are
Benedict Wong344bd622017-11-16 15:27:22 -0800199 * being tracked by this record and to subsequently release this record for garbage
Benedict Wong409c8ca2017-10-26 19:41:43 -0700200 * collection
201 */
202 @Override
203 public void binderDied() {
204 synchronized (IpSecService.this) {
205 try {
206 userRelease();
207 } catch (Exception e) {
208 Log.e(TAG, "Failed to release resource: " + e);
209 }
210 }
211 }
212
213 public T getResource() {
214 return mResource;
215 }
216
217 /**
218 * Unlinks from binder and performs IpSecService resource cleanup (removes from resource
219 * arrays)
220 *
221 * <p>If this method has been previously called, the RefcountedResource's binder field will
222 * be null, and the method will return without performing the cleanup a second time.
223 *
224 * <p>Note that calling this function does not imply that kernel resources will be freed at
225 * this time, or that the related quota will be returned. Such actions will only be
226 * performed upon the reference count reaching zero.
227 */
228 @GuardedBy("IpSecService.this")
229 public void userRelease() throws RemoteException {
230 // Prevent users from putting reference counts into a bad state by calling
231 // userRelease() multiple times.
232 if (mBinder == null) {
233 return;
234 }
235
236 mBinder.unlinkToDeath(this, 0);
237 mBinder = null;
238
239 mResource.invalidate();
240
241 releaseReference();
242 }
243
244 /**
245 * Removes a reference to this resource. If the resultant reference count is zero, the
246 * underlying resources are freed, and references to all child resources are also dropped
247 * recursively (resulting in them freeing their resources and children, etcetera)
248 *
249 * <p>This method also sets the reference count to an invalid value (-1) to signify that it
250 * has been fully released. Any subsequent calls to this method will result in an
251 * IllegalStateException being thrown due to resource already having been previously
252 * released
253 */
254 @VisibleForTesting
255 @GuardedBy("IpSecService.this")
256 public void releaseReference() throws RemoteException {
257 mRefCount--;
258
259 if (mRefCount > 0) {
260 return;
261 } else if (mRefCount < 0) {
262 throw new IllegalStateException(
263 "Invalid operation - resource has already been released.");
264 }
265
266 // Cleanup own resources
267 mResource.freeUnderlyingResources();
268
269 // Cleanup child resources as needed
270 for (RefcountedResource<? extends IResource> child : mChildren) {
271 child.releaseReference();
272 }
273
274 // Enforce that resource cleanup can only be called once
275 // By decrementing the refcount (from 0 to -1), the next call will throw an
276 // IllegalStateException - it has already been released fully.
277 mRefCount--;
278 }
279
280 @Override
281 public String toString() {
282 return new StringBuilder()
283 .append("{mResource=")
284 .append(mResource)
285 .append(", mRefCount=")
286 .append(mRefCount)
287 .append(", mChildren=")
288 .append(mChildren)
289 .append("}")
290 .toString();
291 }
292 }
293
Nathan Harolda1afbd82017-04-24 16:16:34 -0700294 /* Very simple counting class that looks much like a counting semaphore */
Benedict Wong344bd622017-11-16 15:27:22 -0800295 @VisibleForTesting
296 static class ResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700297 private final int mMax;
298 int mCurrent;
299
300 ResourceTracker(int max) {
301 mMax = max;
302 mCurrent = 0;
303 }
304
Benedict Wong344bd622017-11-16 15:27:22 -0800305 boolean isAvailable() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700306 return (mCurrent < mMax);
307 }
308
Benedict Wong344bd622017-11-16 15:27:22 -0800309 void take() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700310 if (!isAvailable()) {
311 Log.wtf(TAG, "Too many resources allocated!");
312 }
313 mCurrent++;
314 }
315
Benedict Wong344bd622017-11-16 15:27:22 -0800316 void give() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700317 if (mCurrent <= 0) {
318 Log.wtf(TAG, "We've released this resource too many times");
319 }
320 mCurrent--;
321 }
ludi3e5ea232017-08-10 15:44:40 -0700322
323 @Override
324 public String toString() {
325 return new StringBuilder()
326 .append("{mCurrent=")
327 .append(mCurrent)
328 .append(", mMax=")
329 .append(mMax)
330 .append("}")
331 .toString();
332 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700333 }
334
Benedict Wong344bd622017-11-16 15:27:22 -0800335 @VisibleForTesting
336 static final class UserRecord {
337 /* Type names */
338 public static final String TYPENAME_SPI = "SecurityParameterIndex";
339 public static final String TYPENAME_TRANSFORM = "IpSecTransform";
340 public static final String TYPENAME_ENCAP_SOCKET = "UdpEncapSocket";
341
342 /* Maximum number of each type of resource that a single UID may possess */
Nathan Harolda1afbd82017-04-24 16:16:34 -0700343 public static final int MAX_NUM_ENCAP_SOCKETS = 2;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700344 public static final int MAX_NUM_TRANSFORMS = 4;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700345 public static final int MAX_NUM_SPIS = 8;
346
Benedict Wong344bd622017-11-16 15:27:22 -0800347 final RefcountedResourceArray<SpiRecord> mSpiRecords =
348 new RefcountedResourceArray<>(TYPENAME_SPI);
349 final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
ludi3e5ea232017-08-10 15:44:40 -0700350
Benedict Wong344bd622017-11-16 15:27:22 -0800351 final RefcountedResourceArray<TransformRecord> mTransformRecords =
352 new RefcountedResourceArray<>(TYPENAME_TRANSFORM);
353 final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
354
355 final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =
356 new RefcountedResourceArray<>(TYPENAME_ENCAP_SOCKET);
357 final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
358
359 void removeSpiRecord(int resourceId) {
360 mSpiRecords.remove(resourceId);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700361 }
362
Benedict Wong344bd622017-11-16 15:27:22 -0800363 void removeTransformRecord(int resourceId) {
364 mTransformRecords.remove(resourceId);
365 }
366
367 void removeEncapSocketRecord(int resourceId) {
368 mEncapSocketRecords.remove(resourceId);
369 }
370
371 @Override
372 public String toString() {
373 return new StringBuilder()
374 .append("{mSpiQuotaTracker=")
375 .append(mSpiQuotaTracker)
376 .append(", mTransformQuotaTracker=")
377 .append(mTransformQuotaTracker)
378 .append(", mSocketQuotaTracker=")
379 .append(mSocketQuotaTracker)
380 .append(", mSpiRecords=")
381 .append(mSpiRecords)
382 .append(", mTransformRecords=")
383 .append(mTransformRecords)
384 .append(", mEncapSocketRecords=")
385 .append(mEncapSocketRecords)
386 .append("}")
387 .toString();
388 }
389 }
390
391 @VisibleForTesting
392 static final class UserResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700393 private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
394
Benedict Wong344bd622017-11-16 15:27:22 -0800395 /** Never-fail getter that populates the list of UIDs as-needed */
396 public UserRecord getUserRecord(int uid) {
397 checkCallerUid(uid);
398
Nathan Harolda1afbd82017-04-24 16:16:34 -0700399 UserRecord r = mUserRecords.get(uid);
400 if (r == null) {
401 r = new UserRecord();
402 mUserRecords.put(uid, r);
403 }
404 return r;
405 }
ludi3e5ea232017-08-10 15:44:40 -0700406
Benedict Wong344bd622017-11-16 15:27:22 -0800407 /** Safety method; guards against access of other user's UserRecords */
408 private void checkCallerUid(int uid) {
409 if (uid != Binder.getCallingUid()
410 && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
411 throw new SecurityException("Attempted access of unowned resources");
412 }
413 }
414
ludi3e5ea232017-08-10 15:44:40 -0700415 @Override
416 public String toString() {
417 return mUserRecords.toString();
418 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700419 }
420
Benedict Wong344bd622017-11-16 15:27:22 -0800421 @VisibleForTesting final UserResourceTracker mUserResourceTracker = new UserResourceTracker();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700422
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700423 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800424 * The KernelResourceRecord class provides a facility to cleanly and reliably track system
425 * resources. It relies on a provided resourceId that should uniquely identify the kernel
426 * resource. To use this class, the user should implement the invalidate() and
427 * freeUnderlyingResources() methods that are responsible for cleaning up IpSecService resource
428 * tracking arrays and kernel resources, respectively
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700429 */
Benedict Wong344bd622017-11-16 15:27:22 -0800430 private abstract class KernelResourceRecord implements IResource {
Nathan Harold93962f32017-03-07 13:23:36 -0800431 final int pid;
432 final int uid;
Benedict Wong344bd622017-11-16 15:27:22 -0800433 protected final int mResourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800434
Benedict Wong344bd622017-11-16 15:27:22 -0800435 KernelResourceRecord(int resourceId) {
Nathan Harold93962f32017-03-07 13:23:36 -0800436 super();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700437 if (resourceId == INVALID_RESOURCE_ID) {
438 throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
439 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700440 mResourceId = resourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800441 pid = Binder.getCallingPid();
442 uid = Binder.getCallingUid();
443
Nathan Harolda1afbd82017-04-24 16:16:34 -0700444 getResourceTracker().take();
Nathan Harold93962f32017-03-07 13:23:36 -0800445 }
446
Benedict Wong344bd622017-11-16 15:27:22 -0800447 @Override
448 public abstract void invalidate() throws RemoteException;
449
450 /** Convenience method; retrieves the user resource record for the stored UID. */
451 protected UserRecord getUserRecord() {
452 return mUserResourceTracker.getUserRecord(uid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700453 }
454
Benedict Wong344bd622017-11-16 15:27:22 -0800455 @Override
456 public abstract void freeUnderlyingResources() throws RemoteException;
ludib0c95b12017-05-22 10:52:23 -0700457
Nathan Harolda1afbd82017-04-24 16:16:34 -0700458 /** Get the resource tracker for this resource */
459 protected abstract ResourceTracker getResourceTracker();
460
ludib0c95b12017-05-22 10:52:23 -0700461 @Override
462 public String toString() {
463 return new StringBuilder()
464 .append("{mResourceId=")
465 .append(mResourceId)
466 .append(", pid=")
467 .append(pid)
468 .append(", uid=")
469 .append(uid)
ludib0c95b12017-05-22 10:52:23 -0700470 .append("}")
471 .toString();
472 }
Nathan Harold93962f32017-03-07 13:23:36 -0800473 };
474
Benedict Wong344bd622017-11-16 15:27:22 -0800475 // TODO: Move this to right after RefcountedResource. With this here, Gerrit was showing many
476 // more things as changed.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700477 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800478 * Thin wrapper over SparseArray to ensure resources exist, and simplify generic typing.
479 *
480 * <p>RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException
481 * if a key is not found during a retrieval process.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700482 */
Benedict Wong344bd622017-11-16 15:27:22 -0800483 static class RefcountedResourceArray<T extends IResource> {
484 SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
485 private final String mTypeName;
Nathan Harold93962f32017-03-07 13:23:36 -0800486
Benedict Wong344bd622017-11-16 15:27:22 -0800487 public RefcountedResourceArray(String typeName) {
488 this.mTypeName = typeName;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700489 }
490
Benedict Wong344bd622017-11-16 15:27:22 -0800491 /**
492 * Accessor method to get inner resource object.
493 *
494 * @throws IllegalArgumentException if no resource with provided key is found.
495 */
496 T getResourceOrThrow(int key) {
497 return getRefcountedResourceOrThrow(key).getResource();
498 }
499
500 /**
501 * Accessor method to get reference counting wrapper.
502 *
503 * @throws IllegalArgumentException if no resource with provided key is found.
504 */
505 RefcountedResource<T> getRefcountedResourceOrThrow(int key) {
506 RefcountedResource<T> resource = mArray.get(key);
507 if (resource == null) {
508 throw new IllegalArgumentException(
509 String.format("No such %s found for given id: %d", mTypeName, key));
510 }
511
512 return resource;
513 }
514
515 void put(int key, RefcountedResource<T> obj) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700516 checkNotNull(obj, "Null resources cannot be added");
517 mArray.put(key, obj);
518 }
519
520 void remove(int key) {
521 mArray.remove(key);
522 }
ludib0c95b12017-05-22 10:52:23 -0700523
524 @Override
525 public String toString() {
526 return mArray.toString();
527 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700528 }
529
Benedict Wong344bd622017-11-16 15:27:22 -0800530 private final class TransformRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700531 private final IpSecConfig mConfig;
532 private final SpiRecord[] mSpis;
Benedict Wong344bd622017-11-16 15:27:22 -0800533 private final EncapSocketRecord mSocket;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700534
535 TransformRecord(
Benedict Wong344bd622017-11-16 15:27:22 -0800536 int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) {
537 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800538 mConfig = config;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700539 mSpis = spis;
540 mSocket = socket;
Nathan Harold93962f32017-03-07 13:23:36 -0800541 }
542
543 public IpSecConfig getConfig() {
544 return mConfig;
545 }
546
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700547 public SpiRecord getSpiRecord(int direction) {
548 return mSpis[direction];
549 }
550
551 /** always guarded by IpSecService#this */
Nathan Harold93962f32017-03-07 13:23:36 -0800552 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800553 public void freeUnderlyingResources() {
Nathan Harold93962f32017-03-07 13:23:36 -0800554 for (int direction : DIRECTIONS) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700555 int spi = mSpis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800556 try {
ludi1a06aa72017-05-12 09:15:00 -0700557 mSrvConfig
558 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800559 .ipSecDeleteSecurityAssociation(
560 mResourceId,
561 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -0700562 mConfig.getLocalAddress(),
563 mConfig.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700564 spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800565 } catch (ServiceSpecificException e) {
566 // FIXME: get the error code and throw is at an IOException from Errno Exception
567 } catch (RemoteException e) {
568 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
569 }
570 }
Nathan Harold93962f32017-03-07 13:23:36 -0800571
Benedict Wong344bd622017-11-16 15:27:22 -0800572 getResourceTracker().give();
Nathan Harold93962f32017-03-07 13:23:36 -0800573 }
ludib0c95b12017-05-22 10:52:23 -0700574
Benedict Wong344bd622017-11-16 15:27:22 -0800575 @Override
576 public void invalidate() throws RemoteException {
577 getUserRecord().removeTransformRecord(mResourceId);
578 }
579
580 @Override
Nathan Harolda1afbd82017-04-24 16:16:34 -0700581 protected ResourceTracker getResourceTracker() {
Benedict Wong344bd622017-11-16 15:27:22 -0800582 return getUserRecord().mTransformQuotaTracker;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700583 }
584
ludib0c95b12017-05-22 10:52:23 -0700585 @Override
586 public String toString() {
587 StringBuilder strBuilder = new StringBuilder();
588 strBuilder
589 .append("{super=")
590 .append(super.toString())
591 .append(", mSocket=")
592 .append(mSocket)
593 .append(", mSpis[OUT].mResourceId=")
594 .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId)
595 .append(", mSpis[IN].mResourceId=")
596 .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId)
597 .append(", mConfig=")
598 .append(mConfig)
599 .append("}");
600 return strBuilder.toString();
601 }
Nathan Harold93962f32017-03-07 13:23:36 -0800602 }
603
Benedict Wong344bd622017-11-16 15:27:22 -0800604 private final class SpiRecord extends KernelResourceRecord {
Nathan Harold93962f32017-03-07 13:23:36 -0800605 private final int mDirection;
606 private final String mLocalAddress;
607 private final String mRemoteAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800608 private int mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700609
610 private boolean mOwnedByTransform = false;
Nathan Harold93962f32017-03-07 13:23:36 -0800611
612 SpiRecord(
613 int resourceId,
614 int direction,
615 String localAddress,
616 String remoteAddress,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700617 int spi) {
Benedict Wong344bd622017-11-16 15:27:22 -0800618 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800619 mDirection = direction;
620 mLocalAddress = localAddress;
621 mRemoteAddress = remoteAddress;
622 mSpi = spi;
Nathan Harold93962f32017-03-07 13:23:36 -0800623 }
624
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700625 /** always guarded by IpSecService#this */
626 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800627 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700628 if (mOwnedByTransform) {
629 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
630 // Because SPIs are "handed off" to transform, objects, they should never be
631 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
632 // thus ownership and responsibility for freeing these resources passes to the
633 // Transform object). Thus, we should let the user free them without penalty once
634 // they are applied in a Transform object.
635 return;
636 }
637
Nathan Harold93962f32017-03-07 13:23:36 -0800638 try {
ludi1a06aa72017-05-12 09:15:00 -0700639 mSrvConfig
640 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800641 .ipSecDeleteSecurityAssociation(
642 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
643 } catch (ServiceSpecificException e) {
644 // FIXME: get the error code and throw is at an IOException from Errno Exception
645 } catch (RemoteException e) {
646 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
647 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700648
649 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800650
Benedict Wong344bd622017-11-16 15:27:22 -0800651 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700652 }
653
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700654 public int getSpi() {
655 return mSpi;
656 }
657
658 public void setOwnedByTransform() {
659 if (mOwnedByTransform) {
660 // Programming error
Andreas Gamped6d8e452017-07-11 10:25:09 -0700661 throw new IllegalStateException("Cannot own an SPI twice!");
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700662 }
663
664 mOwnedByTransform = true;
Nathan Harold93962f32017-03-07 13:23:36 -0800665 }
ludib0c95b12017-05-22 10:52:23 -0700666
667 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800668 public void invalidate() throws RemoteException {
669 getUserRecord().removeSpiRecord(mResourceId);
670 }
671
672 @Override
673 protected ResourceTracker getResourceTracker() {
674 return getUserRecord().mSpiQuotaTracker;
675 }
676
677 @Override
ludib0c95b12017-05-22 10:52:23 -0700678 public String toString() {
679 StringBuilder strBuilder = new StringBuilder();
680 strBuilder
681 .append("{super=")
682 .append(super.toString())
683 .append(", mSpi=")
684 .append(mSpi)
685 .append(", mDirection=")
686 .append(mDirection)
687 .append(", mLocalAddress=")
688 .append(mLocalAddress)
689 .append(", mRemoteAddress=")
690 .append(mRemoteAddress)
691 .append(", mOwnedByTransform=")
692 .append(mOwnedByTransform)
693 .append("}");
694 return strBuilder.toString();
695 }
Nathan Harold93962f32017-03-07 13:23:36 -0800696 }
697
Benedict Wong344bd622017-11-16 15:27:22 -0800698 private final class EncapSocketRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700699 private FileDescriptor mSocket;
700 private final int mPort;
Nathan Harold93962f32017-03-07 13:23:36 -0800701
Benedict Wong344bd622017-11-16 15:27:22 -0800702 EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
703 super(resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700704 mSocket = socket;
705 mPort = port;
706 }
707
708 /** always guarded by IpSecService#this */
709 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800710 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700711 Log.d(TAG, "Closing port " + mPort);
712 IoUtils.closeQuietly(mSocket);
713 mSocket = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700714
Benedict Wong344bd622017-11-16 15:27:22 -0800715 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700716 }
717
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700718 public int getPort() {
719 return mPort;
720 }
721
722 public FileDescriptor getSocket() {
723 return mSocket;
724 }
ludib0c95b12017-05-22 10:52:23 -0700725
726 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800727 protected ResourceTracker getResourceTracker() {
728 return getUserRecord().mSocketQuotaTracker;
729 }
730
731 @Override
732 public void invalidate() {
733 getUserRecord().removeEncapSocketRecord(mResourceId);
734 }
735
736 @Override
ludib0c95b12017-05-22 10:52:23 -0700737 public String toString() {
738 return new StringBuilder()
739 .append("{super=")
740 .append(super.toString())
741 .append(", mSocket=")
742 .append(mSocket)
743 .append(", mPort=")
744 .append(mPort)
745 .append("}")
746 .toString();
747 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700748 }
Nathan Harold93962f32017-03-07 13:23:36 -0800749
Nathan Harold1afbef42017-03-01 18:55:06 -0800750 /**
751 * Constructs a new IpSecService instance
752 *
753 * @param context Binder context for this service
754 */
755 private IpSecService(Context context) {
ludi1a06aa72017-05-12 09:15:00 -0700756 this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
Nathan Harold1afbef42017-03-01 18:55:06 -0800757 }
758
759 static IpSecService create(Context context) throws InterruptedException {
760 final IpSecService service = new IpSecService(context);
761 service.connectNativeNetdService();
762 return service;
763 }
764
ludi1a06aa72017-05-12 09:15:00 -0700765 /** @hide */
766 @VisibleForTesting
767 public IpSecService(Context context, IpSecServiceConfiguration config) {
Benedict Wongbabe5d72017-12-03 19:42:36 -0800768 this(context, config, (fd, uid) -> {
769 try{
770 TrafficStats.setThreadStatsUid(uid);
771 TrafficStats.tagFileDescriptor(fd);
772 } finally {
773 TrafficStats.clearThreadStatsUid();
774 }
775 });
776 }
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) {
841 case IpSecTransform.DIRECTION_OUT:
842 case IpSecTransform.DIRECTION_IN:
843 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 Harold93962f32017-03-07 13:23:36 -0800851 int direction, String remoteAddress, int requestedSpi, IBinder binder)
852 throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -0700853 checkDirection(direction);
854 checkInetAddress(remoteAddress);
855 /* requestedSpi can be anything in the int range, so no check is needed. */
Jonathan Basseri5fb92902017-11-16 10:58:01 -0800856 checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
Nathan Harolda10003d2017-08-23 13:46:33 -0700857
Benedict Wong344bd622017-11-16 15:27:22 -0800858 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harold93962f32017-03-07 13:23:36 -0800859 int resourceId = mNextResourceId.getAndIncrement();
860
861 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
862 String localAddress = "";
Nathan Harolda1afbd82017-04-24 16:16:34 -0700863
Nathan Harold93962f32017-03-07 13:23:36 -0800864 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800865 if (!userRecord.mSpiQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700866 return new IpSecSpiResponse(
Nathan Harolda10003d2017-08-23 13:46:33 -0700867 IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700868 }
Nathan Harold93962f32017-03-07 13:23:36 -0800869 spi =
ludi1a06aa72017-05-12 09:15:00 -0700870 mSrvConfig
871 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800872 .ipSecAllocateSpi(
873 resourceId,
874 direction,
875 localAddress,
876 remoteAddress,
877 requestedSpi);
878 Log.d(TAG, "Allocated SPI " + spi);
Benedict Wong344bd622017-11-16 15:27:22 -0800879 userRecord.mSpiRecords.put(
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700880 resourceId,
Benedict Wong344bd622017-11-16 15:27:22 -0800881 new RefcountedResource<SpiRecord>(
882 new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi),
883 binder));
Nathan Harold93962f32017-03-07 13:23:36 -0800884 } catch (ServiceSpecificException e) {
885 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700886 return new IpSecSpiResponse(
Nathan Harolda1afbd82017-04-24 16:16:34 -0700887 IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800888 } catch (RemoteException e) {
889 throw e.rethrowFromSystemServer();
890 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700891 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
892 }
893
894 /* This method should only be called from Binder threads. Do not call this from
895 * within the system server as it will crash the system on failure.
896 */
Benedict Wong344bd622017-11-16 15:27:22 -0800897 private void releaseResource(RefcountedResourceArray resArray, int resourceId)
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700898 throws RemoteException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700899
Benedict Wong344bd622017-11-16 15:27:22 -0800900 resArray.getRefcountedResourceOrThrow(resourceId).userRelease();
Nathan Harold93962f32017-03-07 13:23:36 -0800901 }
902
903 /** Release a previously allocated SPI that has been registered with the system server */
904 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800905 public synchronized void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
906 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
907 releaseResource(userRecord.mSpiRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700908 }
909
910 /**
911 * This function finds and forcibly binds to a random system port, ensuring that the port cannot
912 * be unbound.
913 *
914 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
915 * a random open port and then bind by number, this function creates a temp socket, binds to a
916 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
917 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
918 * FileHandle.
919 *
920 * <p>The loop in this function handles the inherent race window between un-binding to a port
921 * and re-binding, during which the system could *technically* hand that port out to someone
922 * else.
923 */
Benedict Wongf186d672017-10-10 20:44:28 -0700924 private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700925 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
926 try {
927 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
928 Os.bind(probeSocket, INADDR_ANY, 0);
929 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
930 Os.close(probeSocket);
931 Log.v(TAG, "Binding to port " + port);
932 Os.bind(sockFd, INADDR_ANY, port);
Benedict Wongf186d672017-10-10 20:44:28 -0700933 return port;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700934 } catch (ErrnoException e) {
935 // Someone miraculously claimed the port just after we closed probeSocket.
936 if (e.errno == OsConstants.EADDRINUSE) {
937 continue;
938 }
939 throw e.rethrowAsIOException();
940 }
941 }
942 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
943 }
Nathan Harold93962f32017-03-07 13:23:36 -0800944
945 /**
Benedict Wongbabe5d72017-12-03 19:42:36 -0800946 * Functional interface to do traffic tagging of given sockets to UIDs.
947 *
948 * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
949 * sockets are billed to the UID that the UDP encap socket was created on behalf of.
950 *
951 * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
952 * methods that cannot be easily mocked/tested.
953 */
954 @VisibleForTesting
955 public interface UidFdTagger {
956 /**
957 * Sets socket tag to assign all traffic to the provided UID.
958 *
959 * <p>Since the socket is created on behalf of an unprivileged application, all traffic
960 * should be accounted to the UID of the unprivileged application.
961 */
962 public void tag(FileDescriptor fd, int uid) throws IOException;
963 }
964
965 /**
Nathan Harold93962f32017-03-07 13:23:36 -0800966 * Open a socket via the system server and bind it to the specified port (random if port=0).
967 * This will return a PFD to the user that represent a bound UDP socket. The system server will
968 * cache the socket and a record of its owner so that it can and must be freed when no longer
969 * needed.
970 */
971 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700972 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
973 throws RemoteException {
974 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
975 throw new IllegalArgumentException(
976 "Specified port number must be a valid non-reserved UDP port");
977 }
Nathan Harolda10003d2017-08-23 13:46:33 -0700978 checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
979
Benedict Wongbabe5d72017-12-03 19:42:36 -0800980 int callingUid = Binder.getCallingUid();
981 UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700982 int resourceId = mNextResourceId.getAndIncrement();
983 FileDescriptor sockFd = null;
984 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800985 if (!userRecord.mSocketQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700986 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
987 }
988
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700989 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Benedict Wongbabe5d72017-12-03 19:42:36 -0800990 mUidFdTagger.tag(sockFd, callingUid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700991
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700992 // This code is common to both the unspecified and specified port cases
993 Os.setsockoptInt(
994 sockFd,
995 OsConstants.IPPROTO_UDP,
996 OsConstants.UDP_ENCAP,
997 OsConstants.UDP_ENCAP_ESPINUDP);
998
Benedict Wongba8d3132017-12-06 21:56:35 -0800999 mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
1000 if (port != 0) {
1001 Log.v(TAG, "Binding to port " + port);
1002 Os.bind(sockFd, INADDR_ANY, port);
1003 } else {
1004 port = bindToRandomPort(sockFd);
1005 }
1006
Benedict Wong344bd622017-11-16 15:27:22 -08001007 userRecord.mEncapSocketRecords.put(
1008 resourceId,
1009 new RefcountedResource<EncapSocketRecord>(
1010 new EncapSocketRecord(resourceId, sockFd, port), binder));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001011 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
1012 } catch (IOException | ErrnoException e) {
1013 IoUtils.closeQuietly(sockFd);
1014 }
1015 // If we make it to here, then something has gone wrong and we couldn't open a socket.
1016 // The only reasonable condition that would cause that is resource unavailable.
1017 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001018 }
1019
1020 /** close a socket that has been been allocated by and registered with the system server */
1021 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001022 public synchronized void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
1023 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1024 releaseResource(userRecord.mEncapSocketRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001025 }
Nathan Harold93962f32017-03-07 13:23:36 -08001026
Benedict Wong4f255702017-11-06 20:49:10 -08001027 @VisibleForTesting
1028 void validateAlgorithms(IpSecConfig config, int direction) throws IllegalArgumentException {
1029 IpSecAlgorithm auth = config.getAuthentication(direction);
1030 IpSecAlgorithm crypt = config.getEncryption(direction);
1031 IpSecAlgorithm aead = config.getAuthenticatedEncryption(direction);
1032
1033 // Validate the algorithm set
1034 Preconditions.checkArgument(
1035 aead != null || crypt != null || auth != null,
1036 "No Encryption or Authentication algorithms specified");
1037 Preconditions.checkArgument(
1038 auth == null || auth.isAuthentication(),
1039 "Unsupported algorithm for Authentication");
1040 Preconditions.checkArgument(
1041 crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption");
1042 Preconditions.checkArgument(
1043 aead == null || aead.isAead(),
1044 "Unsupported algorithm for Authenticated Encryption");
1045 Preconditions.checkArgument(
1046 aead == null || (auth == null && crypt == null),
1047 "Authenticated Encryption is mutually exclusive with other Authentication "
1048 + "or Encryption algorithms");
1049 }
1050
Nathan Harold93962f32017-03-07 13:23:36 -08001051 /**
Nathan Harolda10003d2017-08-23 13:46:33 -07001052 * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
1053 * IllegalArgumentException if they are not.
1054 */
1055 private void checkIpSecConfig(IpSecConfig config) {
Benedict Wong344bd622017-11-16 15:27:22 -08001056 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1057
Nathan Harolda10003d2017-08-23 13:46:33 -07001058 if (config.getLocalAddress() == null) {
1059 throw new IllegalArgumentException("Invalid null Local InetAddress");
1060 }
1061
1062 if (config.getRemoteAddress() == null) {
1063 throw new IllegalArgumentException("Invalid null Remote InetAddress");
1064 }
1065
1066 switch (config.getMode()) {
1067 case IpSecTransform.MODE_TRANSPORT:
1068 if (!config.getLocalAddress().isEmpty()) {
1069 throw new IllegalArgumentException("Non-empty Local Address");
1070 }
1071 // Must be valid, and not a wildcard
1072 checkInetAddress(config.getRemoteAddress());
1073 break;
1074 case IpSecTransform.MODE_TUNNEL:
1075 break;
1076 default:
1077 throw new IllegalArgumentException(
1078 "Invalid IpSecTransform.mode: " + config.getMode());
1079 }
1080
1081 switch (config.getEncapType()) {
1082 case IpSecTransform.ENCAP_NONE:
1083 break;
1084 case IpSecTransform.ENCAP_ESPINUDP:
1085 case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
Benedict Wong344bd622017-11-16 15:27:22 -08001086 // Retrieve encap socket record; will throw IllegalArgumentException if not found
1087 userRecord.mEncapSocketRecords.getResourceOrThrow(
1088 config.getEncapSocketResourceId());
Nathan Harolda10003d2017-08-23 13:46:33 -07001089
1090 int port = config.getEncapRemotePort();
1091 if (port <= 0 || port > 0xFFFF) {
1092 throw new IllegalArgumentException("Invalid remote UDP port: " + port);
1093 }
1094 break;
1095 default:
1096 throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
1097 }
1098
1099 for (int direction : DIRECTIONS) {
Benedict Wong4f255702017-11-06 20:49:10 -08001100 validateAlgorithms(config, direction);
Nathan Harolda10003d2017-08-23 13:46:33 -07001101
Benedict Wong344bd622017-11-16 15:27:22 -08001102 // Retrieve SPI record; will throw IllegalArgumentException if not found
1103 userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction));
Nathan Harolda10003d2017-08-23 13:46:33 -07001104 }
1105 }
1106
1107 /**
Nathan Harold93962f32017-03-07 13:23:36 -08001108 * Create a transport mode transform, which represent two security associations (one in each
1109 * direction) in the kernel. The transform will be cached by the system server and must be freed
1110 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
1111 * that are using it, which will result in all of those sockets becoming unable to send or
1112 * receive data.
1113 */
1114 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001115 public synchronized IpSecTransformResponse createTransportModeTransform(
1116 IpSecConfig c, IBinder binder) throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -07001117 checkIpSecConfig(c);
1118 checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
Nathan Harold93962f32017-03-07 13:23:36 -08001119 int resourceId = mNextResourceId.getAndIncrement();
Benedict Wong344bd622017-11-16 15:27:22 -08001120
1121 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1122
1123 // Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs)
1124 List<RefcountedResource> dependencies = new ArrayList<>(3);
1125
1126 if (!userRecord.mTransformQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -07001127 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
1128 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001129 SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
Nathan Harolda10003d2017-08-23 13:46:33 -07001130
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001131 int encapType, encapLocalPort = 0, encapRemotePort = 0;
Benedict Wong344bd622017-11-16 15:27:22 -08001132 EncapSocketRecord socketRecord = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001133 encapType = c.getEncapType();
1134 if (encapType != IpSecTransform.ENCAP_NONE) {
Benedict Wong344bd622017-11-16 15:27:22 -08001135 RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
1136 userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
1137 c.getEncapSocketResourceId());
1138 dependencies.add(refcountedSocketRecord);
1139
1140 socketRecord = refcountedSocketRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001141 encapLocalPort = socketRecord.getPort();
1142 encapRemotePort = c.getEncapRemotePort();
1143 }
1144
Nathan Harold93962f32017-03-07 13:23:36 -08001145 for (int direction : DIRECTIONS) {
1146 IpSecAlgorithm auth = c.getAuthentication(direction);
1147 IpSecAlgorithm crypt = c.getEncryption(direction);
Benedict Wong0febe5e2017-08-22 21:42:33 -07001148 IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001149
Benedict Wong344bd622017-11-16 15:27:22 -08001150 RefcountedResource<SpiRecord> refcountedSpiRecord =
1151 userRecord.mSpiRecords.getRefcountedResourceOrThrow(
1152 c.getSpiResourceId(direction));
1153 dependencies.add(refcountedSpiRecord);
1154
1155 spis[direction] = refcountedSpiRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001156 int spi = spis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -08001157 try {
Nathan Harolda10003d2017-08-23 13:46:33 -07001158 mSrvConfig
1159 .getNetdInstance()
ludi0f807892017-05-20 14:15:09 -07001160 .ipSecAddSecurityAssociation(
1161 resourceId,
1162 c.getMode(),
1163 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001164 c.getLocalAddress(),
1165 c.getRemoteAddress(),
1166 (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
ludi0f807892017-05-20 14:15:09 -07001167 spi,
1168 (auth != null) ? auth.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001169 (auth != null) ? auth.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001170 (auth != null) ? auth.getTruncationLengthBits() : 0,
1171 (crypt != null) ? crypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001172 (crypt != null) ? crypt.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001173 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
Benedict Wong0febe5e2017-08-22 21:42:33 -07001174 (authCrypt != null) ? authCrypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001175 (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
Benedict Wong0febe5e2017-08-22 21:42:33 -07001176 (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
ludi0f807892017-05-20 14:15:09 -07001177 encapType,
1178 encapLocalPort,
1179 encapRemotePort);
Nathan Harold93962f32017-03-07 13:23:36 -08001180 } catch (ServiceSpecificException e) {
1181 // FIXME: get the error code and throw is at an IOException from Errno Exception
ludi0f807892017-05-20 14:15:09 -07001182 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001183 }
1184 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001185 // Both SAs were created successfully, time to construct a record and lock it away
Benedict Wong344bd622017-11-16 15:27:22 -08001186 userRecord.mTransformRecords.put(
1187 resourceId,
1188 new RefcountedResource<TransformRecord>(
1189 new TransformRecord(resourceId, c, spis, socketRecord),
1190 binder,
1191 dependencies.toArray(new RefcountedResource[dependencies.size()])));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001192 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001193 }
1194
1195 /**
1196 * Delete a transport mode transform that was previously allocated by + registered with the
1197 * system server. If this is called on an inactive (or non-existent) transform, it will not
1198 * return an error. It's safe to de-allocate transforms that may have already been deleted for
1199 * other reasons.
1200 */
1201 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001202 public synchronized void deleteTransportModeTransform(int resourceId) throws RemoteException {
1203 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1204 releaseResource(userRecord.mTransformRecords, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001205 }
1206
1207 /**
1208 * Apply an active transport mode transform to a socket, which will apply the IPsec security
1209 * association as a correspondent policy to the provided socket
1210 */
1211 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001212 public synchronized void applyTransportModeTransform(
1213 ParcelFileDescriptor socket, int resourceId) throws RemoteException {
Benedict Wong344bd622017-11-16 15:27:22 -08001214 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harold93962f32017-03-07 13:23:36 -08001215
Benedict Wong344bd622017-11-16 15:27:22 -08001216 // Get transform record; if no transform is found, will throw IllegalArgumentException
1217 TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001218
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001219 // TODO: make this a function.
1220 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
1221 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
1222 }
1223
1224 IpSecConfig c = info.getConfig();
1225 try {
1226 for (int direction : DIRECTIONS) {
ludi1a06aa72017-05-12 09:15:00 -07001227 mSrvConfig
1228 .getNetdInstance()
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001229 .ipSecApplyTransportModeTransform(
1230 socket.getFileDescriptor(),
1231 resourceId,
1232 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001233 c.getLocalAddress(),
1234 c.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001235 info.getSpiRecord(direction).getSpi());
Nathan Harold93962f32017-03-07 13:23:36 -08001236 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001237 } catch (ServiceSpecificException e) {
1238 // FIXME: get the error code and throw is at an IOException from Errno Exception
Nathan Harold93962f32017-03-07 13:23:36 -08001239 }
1240 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001241
Nathan Harold93962f32017-03-07 13:23:36 -08001242 /**
1243 * Remove a transport mode transform from a socket, applying the default (empty) policy. This
1244 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
1245 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
1246 * used: reserved for future improved input validation.
1247 */
1248 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001249 public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
Nathan Harold93962f32017-03-07 13:23:36 -08001250 throws RemoteException {
1251 try {
ludi1a06aa72017-05-12 09:15:00 -07001252 mSrvConfig
1253 .getNetdInstance()
1254 .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
Nathan Harold93962f32017-03-07 13:23:36 -08001255 } catch (ServiceSpecificException e) {
1256 // FIXME: get the error code and throw is at an IOException from Errno Exception
1257 }
1258 }
1259
1260 @Override
ludib0c95b12017-05-22 10:52:23 -07001261 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Nathan Harold1afbef42017-03-01 18:55:06 -08001262 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
ludib0c95b12017-05-22 10:52:23 -07001263
1264 pw.println("IpSecService dump:");
Nathan Harold1afbef42017-03-01 18:55:06 -08001265 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
1266 pw.println();
ludib0c95b12017-05-22 10:52:23 -07001267
Benedict Wong344bd622017-11-16 15:27:22 -08001268 pw.println("mUserResourceTracker:");
1269 pw.println(mUserResourceTracker);
Nathan Harold1afbef42017-03-01 18:55:06 -08001270 }
1271}