blob: ab7daccb0691e683011fc84009b6481bee0bdfb6 [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 Harold1afbef42017-03-01 18:55:06 -080036import android.net.util.NetdService;
Nathan Harold93962f32017-03-07 13:23:36 -080037import android.os.Binder;
Nathan Harold93962f32017-03-07 13:23:36 -080038import android.os.IBinder;
39import android.os.ParcelFileDescriptor;
Nathan Harold1afbef42017-03-01 18:55:06 -080040import android.os.RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -080041import android.os.ServiceSpecificException;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070042import android.system.ErrnoException;
43import android.system.Os;
44import android.system.OsConstants;
Nathan Harold1afbef42017-03-01 18:55:06 -080045import android.util.Log;
46import android.util.Slog;
Nathan Harold93962f32017-03-07 13:23:36 -080047import android.util.SparseArray;
48import com.android.internal.annotations.GuardedBy;
ludi1a06aa72017-05-12 09:15:00 -070049import com.android.internal.annotations.VisibleForTesting;
Nathan Harold1afbef42017-03-01 18:55:06 -080050import java.io.FileDescriptor;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070051import java.io.IOException;
Nathan Harold1afbef42017-03-01 18:55:06 -080052import java.io.PrintWriter;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070053import java.net.InetAddress;
54import java.net.InetSocketAddress;
55import java.net.UnknownHostException;
Nathan Harold93962f32017-03-07 13:23:36 -080056import java.util.concurrent.atomic.AtomicInteger;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070057import libcore.io.IoUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080058
59/** @hide */
60public class IpSecService extends IIpSecService.Stub {
61 private static final String TAG = "IpSecService";
62 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -070063
Nathan Harold1afbef42017-03-01 18:55:06 -080064 private static final String NETD_SERVICE_NAME = "netd";
Nathan Harold93962f32017-03-07 13:23:36 -080065 private static final int[] DIRECTIONS =
66 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
Nathan Harold1afbef42017-03-01 18:55:06 -080067
ludi1a06aa72017-05-12 09:15:00 -070068 private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
Nathan Harold8dc1fd02017-04-04 19:37:48 -070069 private static final int MAX_PORT_BIND_ATTEMPTS = 10;
70 private static final InetAddress INADDR_ANY;
71
72 static {
73 try {
74 INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
75 } catch (UnknownHostException e) {
76 throw new RuntimeException(e);
77 }
78 }
79
80 static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
81 static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
82
83 /* Binder context for this service */
Nathan Harold1afbef42017-03-01 18:55:06 -080084 private final Context mContext;
85
Nathan Harold8dc1fd02017-04-04 19:37:48 -070086 /** Should be a never-repeating global ID for resources */
87 private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
Nathan Harold1afbef42017-03-01 18:55:06 -080088
Nathan Harold8dc1fd02017-04-04 19:37:48 -070089 @GuardedBy("this")
90 private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>();
Nathan Harold1afbef42017-03-01 18:55:06 -080091
Nathan Harold8dc1fd02017-04-04 19:37:48 -070092 @GuardedBy("this")
93 private final ManagedResourceArray<TransformRecord> mTransformRecords =
94 new ManagedResourceArray<>();
Nathan Harold93962f32017-03-07 13:23:36 -080095
Nathan Harold8dc1fd02017-04-04 19:37:48 -070096 @GuardedBy("this")
97 private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords =
98 new ManagedResourceArray<>();
99
ludi1a06aa72017-05-12 09:15:00 -0700100 interface IpSecServiceConfiguration {
101 INetd getNetdInstance() throws RemoteException;
102
103 static IpSecServiceConfiguration GETSRVINSTANCE =
104 new IpSecServiceConfiguration() {
105 @Override
106 public INetd getNetdInstance() throws RemoteException {
107 final INetd netd = NetdService.getInstance();
108 if (netd == null) {
109 throw new RemoteException("Failed to Get Netd Instance");
110 }
111 return netd;
112 }
113 };
114 }
115
116 private final IpSecServiceConfiguration mSrvConfig;
117
Nathan Harolda1afbd82017-04-24 16:16:34 -0700118 /* Very simple counting class that looks much like a counting semaphore */
119 public static class ResourceTracker {
120 private final int mMax;
121 int mCurrent;
122
123 ResourceTracker(int max) {
124 mMax = max;
125 mCurrent = 0;
126 }
127
128 synchronized boolean isAvailable() {
129 return (mCurrent < mMax);
130 }
131
132 synchronized void take() {
133 if (!isAvailable()) {
134 Log.wtf(TAG, "Too many resources allocated!");
135 }
136 mCurrent++;
137 }
138
139 synchronized void give() {
140 if (mCurrent <= 0) {
141 Log.wtf(TAG, "We've released this resource too many times");
142 }
143 mCurrent--;
144 }
145 }
146
147 private static final class UserQuotaTracker {
148 /* Maximum number of UDP Encap Sockets that a single UID may possess */
149 public static final int MAX_NUM_ENCAP_SOCKETS = 2;
150
151 /* Maximum number of IPsec Transforms that a single UID may possess */
152 public static final int MAX_NUM_TRANSFORMS = 4;
153
154 /* Maximum number of IPsec Transforms that a single UID may possess */
155 public static final int MAX_NUM_SPIS = 8;
156
157 /* Record for one users's IpSecService-managed objects */
158 public static class UserRecord {
159 public final ResourceTracker socket = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
160 public final ResourceTracker transform = new ResourceTracker(MAX_NUM_TRANSFORMS);
161 public final ResourceTracker spi = new ResourceTracker(MAX_NUM_SPIS);
162 }
163
164 private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
165
166 /* a never-fail getter so that we can populate the list of UIDs as-needed */
167 public synchronized UserRecord getUserRecord(int uid) {
168 UserRecord r = mUserRecords.get(uid);
169 if (r == null) {
170 r = new UserRecord();
171 mUserRecords.put(uid, r);
172 }
173 return r;
174 }
175 }
176
177 private final UserQuotaTracker mUserQuotaTracker = new UserQuotaTracker();
178
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700179 /**
180 * The ManagedResource class provides a facility to cleanly and reliably release system
181 * resources. It relies on two things: an IBinder that allows ManagedResource to automatically
182 * clean up in the event that the Binder dies and a user-provided resourceId that should
183 * uniquely identify the managed resource. To use this class, the user should implement the
184 * releaseResources() method that is responsible for releasing system resources when invoked.
185 */
Nathan Harold93962f32017-03-07 13:23:36 -0800186 private abstract class ManagedResource implements IBinder.DeathRecipient {
187 final int pid;
188 final int uid;
189 private IBinder mBinder;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700190 protected int mResourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800191
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700192 private AtomicInteger mReferenceCount = new AtomicInteger(0);
193
194 ManagedResource(int resourceId, IBinder binder) {
Nathan Harold93962f32017-03-07 13:23:36 -0800195 super();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700196 if (resourceId == INVALID_RESOURCE_ID) {
197 throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
198 }
Nathan Harold93962f32017-03-07 13:23:36 -0800199 mBinder = binder;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700200 mResourceId = resourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800201 pid = Binder.getCallingPid();
202 uid = Binder.getCallingUid();
203
Nathan Harolda1afbd82017-04-24 16:16:34 -0700204 getResourceTracker().take();
Nathan Harold93962f32017-03-07 13:23:36 -0800205 try {
206 mBinder.linkToDeath(this, 0);
207 } catch (RemoteException e) {
208 binderDied();
209 }
210 }
211
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700212 public void addReference() {
213 mReferenceCount.incrementAndGet();
214 }
215
216 public void removeReference() {
217 if (mReferenceCount.decrementAndGet() < 0) {
218 Log.wtf(TAG, "Programming error: negative reference count");
219 }
220 }
221
222 public boolean isReferenced() {
223 return (mReferenceCount.get() > 0);
224 }
225
226 public void checkOwnerOrSystemAndThrow() {
227 if (uid != Binder.getCallingUid()
228 && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
229 throw new SecurityException("Only the owner may access managed resources!");
230 }
231 }
232
Nathan Harold93962f32017-03-07 13:23:36 -0800233 /**
234 * When this record is no longer needed for managing system resources this function should
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700235 * clean up all system resources and nullify the record. This function shall perform all
236 * necessary cleanup of the resources managed by this record.
237 *
238 * <p>NOTE: this function verifies ownership before allowing resources to be freed.
Nathan Harold93962f32017-03-07 13:23:36 -0800239 */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700240 public final void release() throws RemoteException {
241 synchronized (IpSecService.this) {
242 if (isReferenced()) {
243 throw new IllegalStateException(
244 "Cannot release a resource that has active references!");
245 }
Nathan Harold93962f32017-03-07 13:23:36 -0800246
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700247 if (mResourceId == INVALID_RESOURCE_ID) {
248 return;
249 }
250
251 releaseResources();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700252 getResourceTracker().give();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700253 if (mBinder != null) {
254 mBinder.unlinkToDeath(this, 0);
255 }
256 mBinder = null;
257
258 mResourceId = INVALID_RESOURCE_ID;
Nathan Harold93962f32017-03-07 13:23:36 -0800259 }
Nathan Harold93962f32017-03-07 13:23:36 -0800260 }
261
262 /**
263 * If the Binder object dies, this function is called to free the system resources that are
264 * being managed by this record and to subsequently release this record for garbage
265 * collection
266 */
267 public final void binderDied() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700268 try {
269 release();
270 } catch (Exception e) {
271 Log.e(TAG, "Failed to release resource: " + e);
272 }
Nathan Harold93962f32017-03-07 13:23:36 -0800273 }
274
275 /**
Nathan Harold93962f32017-03-07 13:23:36 -0800276 * Implement this method to release all system resources that are being protected by this
277 * record. Once the resources are released, the record should be invalidated and no longer
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700278 * used by calling release(). This should NEVER be called directly.
279 *
280 * <p>Calls to this are always guarded by IpSecService#this
Nathan Harold93962f32017-03-07 13:23:36 -0800281 */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700282 protected abstract void releaseResources() throws RemoteException;
ludib0c95b12017-05-22 10:52:23 -0700283
Nathan Harolda1afbd82017-04-24 16:16:34 -0700284 /** Get the resource tracker for this resource */
285 protected abstract ResourceTracker getResourceTracker();
286
ludib0c95b12017-05-22 10:52:23 -0700287 @Override
288 public String toString() {
289 return new StringBuilder()
290 .append("{mResourceId=")
291 .append(mResourceId)
292 .append(", pid=")
293 .append(pid)
294 .append(", uid=")
295 .append(uid)
296 .append(", mReferenceCount=")
297 .append(mReferenceCount.get())
298 .append("}")
299 .toString();
300 }
Nathan Harold93962f32017-03-07 13:23:36 -0800301 };
302
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700303 /**
ludi1a06aa72017-05-12 09:15:00 -0700304 * Minimal wrapper around SparseArray that performs ownership validation on element accesses.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700305 */
306 private class ManagedResourceArray<T extends ManagedResource> {
307 SparseArray<T> mArray = new SparseArray<>();
Nathan Harold93962f32017-03-07 13:23:36 -0800308
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700309 T get(int key) {
310 T val = mArray.get(key);
Nathan Haroldb6d2eff2017-07-06 16:57:51 -0700311 // The value should never be null unless the resource doesn't exist
312 // (since we do not allow null resources to be added).
313 if (val != null) {
314 val.checkOwnerOrSystemAndThrow();
315 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700316 return val;
317 }
318
319 void put(int key, T obj) {
320 checkNotNull(obj, "Null resources cannot be added");
321 mArray.put(key, obj);
322 }
323
324 void remove(int key) {
325 mArray.remove(key);
326 }
ludib0c95b12017-05-22 10:52:23 -0700327
328 @Override
329 public String toString() {
330 return mArray.toString();
331 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700332 }
333
334 private final class TransformRecord extends ManagedResource {
335 private final IpSecConfig mConfig;
336 private final SpiRecord[] mSpis;
337 private final UdpSocketRecord mSocket;
338
339 TransformRecord(
340 int resourceId,
341 IBinder binder,
342 IpSecConfig config,
343 SpiRecord[] spis,
344 UdpSocketRecord socket) {
345 super(resourceId, binder);
Nathan Harold93962f32017-03-07 13:23:36 -0800346 mConfig = config;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700347 mSpis = spis;
348 mSocket = socket;
349
350 for (int direction : DIRECTIONS) {
351 mSpis[direction].addReference();
352 mSpis[direction].setOwnedByTransform();
353 }
354
355 if (mSocket != null) {
356 mSocket.addReference();
357 }
Nathan Harold93962f32017-03-07 13:23:36 -0800358 }
359
360 public IpSecConfig getConfig() {
361 return mConfig;
362 }
363
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700364 public SpiRecord getSpiRecord(int direction) {
365 return mSpis[direction];
366 }
367
368 /** always guarded by IpSecService#this */
Nathan Harold93962f32017-03-07 13:23:36 -0800369 @Override
370 protected void releaseResources() {
371 for (int direction : DIRECTIONS) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700372 int spi = mSpis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800373 try {
ludi1a06aa72017-05-12 09:15:00 -0700374 mSrvConfig
375 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800376 .ipSecDeleteSecurityAssociation(
377 mResourceId,
378 direction,
379 (mConfig.getLocalAddress() != null)
380 ? mConfig.getLocalAddress().getHostAddress()
381 : "",
382 (mConfig.getRemoteAddress() != null)
383 ? mConfig.getRemoteAddress().getHostAddress()
384 : "",
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700385 spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800386 } catch (ServiceSpecificException e) {
387 // FIXME: get the error code and throw is at an IOException from Errno Exception
388 } catch (RemoteException e) {
389 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
390 }
391 }
Nathan Harold93962f32017-03-07 13:23:36 -0800392
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700393 for (int direction : DIRECTIONS) {
394 mSpis[direction].removeReference();
395 }
396
397 if (mSocket != null) {
398 mSocket.removeReference();
399 }
Nathan Harold93962f32017-03-07 13:23:36 -0800400 }
ludib0c95b12017-05-22 10:52:23 -0700401
Nathan Harolda1afbd82017-04-24 16:16:34 -0700402 protected ResourceTracker getResourceTracker() {
403 return mUserQuotaTracker.getUserRecord(this.uid).transform;
404 }
405
ludib0c95b12017-05-22 10:52:23 -0700406 @Override
407 public String toString() {
408 StringBuilder strBuilder = new StringBuilder();
409 strBuilder
410 .append("{super=")
411 .append(super.toString())
412 .append(", mSocket=")
413 .append(mSocket)
414 .append(", mSpis[OUT].mResourceId=")
415 .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId)
416 .append(", mSpis[IN].mResourceId=")
417 .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId)
418 .append(", mConfig=")
419 .append(mConfig)
420 .append("}");
421 return strBuilder.toString();
422 }
Nathan Harold93962f32017-03-07 13:23:36 -0800423 }
424
425 private final class SpiRecord extends ManagedResource {
426 private final int mDirection;
427 private final String mLocalAddress;
428 private final String mRemoteAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800429 private int mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700430
431 private boolean mOwnedByTransform = false;
Nathan Harold93962f32017-03-07 13:23:36 -0800432
433 SpiRecord(
434 int resourceId,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700435 IBinder binder,
Nathan Harold93962f32017-03-07 13:23:36 -0800436 int direction,
437 String localAddress,
438 String remoteAddress,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700439 int spi) {
440 super(resourceId, binder);
Nathan Harold93962f32017-03-07 13:23:36 -0800441 mDirection = direction;
442 mLocalAddress = localAddress;
443 mRemoteAddress = remoteAddress;
444 mSpi = spi;
Nathan Harold93962f32017-03-07 13:23:36 -0800445 }
446
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700447 /** always guarded by IpSecService#this */
448 @Override
Nathan Harold93962f32017-03-07 13:23:36 -0800449 protected void releaseResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700450 if (mOwnedByTransform) {
451 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
452 // Because SPIs are "handed off" to transform, objects, they should never be
453 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
454 // thus ownership and responsibility for freeing these resources passes to the
455 // Transform object). Thus, we should let the user free them without penalty once
456 // they are applied in a Transform object.
457 return;
458 }
459
Nathan Harold93962f32017-03-07 13:23:36 -0800460 try {
ludi1a06aa72017-05-12 09:15:00 -0700461 mSrvConfig
462 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800463 .ipSecDeleteSecurityAssociation(
464 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
465 } catch (ServiceSpecificException e) {
466 // FIXME: get the error code and throw is at an IOException from Errno Exception
467 } catch (RemoteException e) {
468 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
469 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700470
471 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800472 }
473
Nathan Harolda1afbd82017-04-24 16:16:34 -0700474 @Override
475 protected ResourceTracker getResourceTracker() {
476 return mUserQuotaTracker.getUserRecord(this.uid).spi;
477 }
478
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700479 public int getSpi() {
480 return mSpi;
481 }
482
483 public void setOwnedByTransform() {
484 if (mOwnedByTransform) {
485 // Programming error
Andreas Gamped6d8e452017-07-11 10:25:09 -0700486 throw new IllegalStateException("Cannot own an SPI twice!");
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700487 }
488
489 mOwnedByTransform = true;
Nathan Harold93962f32017-03-07 13:23:36 -0800490 }
ludib0c95b12017-05-22 10:52:23 -0700491
492 @Override
493 public String toString() {
494 StringBuilder strBuilder = new StringBuilder();
495 strBuilder
496 .append("{super=")
497 .append(super.toString())
498 .append(", mSpi=")
499 .append(mSpi)
500 .append(", mDirection=")
501 .append(mDirection)
502 .append(", mLocalAddress=")
503 .append(mLocalAddress)
504 .append(", mRemoteAddress=")
505 .append(mRemoteAddress)
506 .append(", mOwnedByTransform=")
507 .append(mOwnedByTransform)
508 .append("}");
509 return strBuilder.toString();
510 }
Nathan Harold93962f32017-03-07 13:23:36 -0800511 }
512
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700513 private final class UdpSocketRecord extends ManagedResource {
514 private FileDescriptor mSocket;
515 private final int mPort;
Nathan Harold93962f32017-03-07 13:23:36 -0800516
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700517 UdpSocketRecord(int resourceId, IBinder binder, FileDescriptor socket, int port) {
518 super(resourceId, binder);
519 mSocket = socket;
520 mPort = port;
521 }
522
523 /** always guarded by IpSecService#this */
524 @Override
525 protected void releaseResources() {
526 Log.d(TAG, "Closing port " + mPort);
527 IoUtils.closeQuietly(mSocket);
528 mSocket = null;
529 }
530
Nathan Harolda1afbd82017-04-24 16:16:34 -0700531 @Override
532 protected ResourceTracker getResourceTracker() {
533 return mUserQuotaTracker.getUserRecord(this.uid).socket;
534 }
535
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700536 public int getPort() {
537 return mPort;
538 }
539
540 public FileDescriptor getSocket() {
541 return mSocket;
542 }
ludib0c95b12017-05-22 10:52:23 -0700543
544 @Override
545 public String toString() {
546 return new StringBuilder()
547 .append("{super=")
548 .append(super.toString())
549 .append(", mSocket=")
550 .append(mSocket)
551 .append(", mPort=")
552 .append(mPort)
553 .append("}")
554 .toString();
555 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700556 }
Nathan Harold93962f32017-03-07 13:23:36 -0800557
Nathan Harold1afbef42017-03-01 18:55:06 -0800558 /**
559 * Constructs a new IpSecService instance
560 *
561 * @param context Binder context for this service
562 */
563 private IpSecService(Context context) {
ludi1a06aa72017-05-12 09:15:00 -0700564 this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
Nathan Harold1afbef42017-03-01 18:55:06 -0800565 }
566
567 static IpSecService create(Context context) throws InterruptedException {
568 final IpSecService service = new IpSecService(context);
569 service.connectNativeNetdService();
570 return service;
571 }
572
ludi1a06aa72017-05-12 09:15:00 -0700573 /** @hide */
574 @VisibleForTesting
575 public IpSecService(Context context, IpSecServiceConfiguration config) {
576 mContext = context;
577 mSrvConfig = config;
578 }
579
Nathan Harold1afbef42017-03-01 18:55:06 -0800580 public void systemReady() {
581 if (isNetdAlive()) {
582 Slog.d(TAG, "IpSecService is ready");
583 } else {
584 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
585 }
586 }
587
588 private void connectNativeNetdService() {
589 // Avoid blocking the system server to do this
Nathan Haroldb0e05082017-07-17 14:01:53 -0700590 new Thread() {
591 @Override
592 public void run() {
593 synchronized (IpSecService.this) {
ludi1a06aa72017-05-12 09:15:00 -0700594 NetdService.get(NETD_FETCH_TIMEOUT_MS);
Nathan Haroldb0e05082017-07-17 14:01:53 -0700595 }
596 }
597 }.start();
Nathan Harold1afbef42017-03-01 18:55:06 -0800598 }
599
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700600 synchronized boolean isNetdAlive() {
601 try {
ludi1a06aa72017-05-12 09:15:00 -0700602 final INetd netd = mSrvConfig.getNetdInstance();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700603 if (netd == null) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800604 return false;
605 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700606 return netd.isAlive();
607 } catch (RemoteException re) {
608 return false;
Nathan Harold1afbef42017-03-01 18:55:06 -0800609 }
610 }
611
612 @Override
Nathan Harold93962f32017-03-07 13:23:36 -0800613 /** Get a new SPI and maintain the reservation in the system server */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700614 public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
Nathan Harold93962f32017-03-07 13:23:36 -0800615 int direction, String remoteAddress, int requestedSpi, IBinder binder)
616 throws RemoteException {
617 int resourceId = mNextResourceId.getAndIncrement();
618
619 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
620 String localAddress = "";
Nathan Harolda1afbd82017-04-24 16:16:34 -0700621
Nathan Harold93962f32017-03-07 13:23:36 -0800622 try {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700623 if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).spi.isAvailable()) {
624 return new IpSecSpiResponse(
625 IpSecManager.Status.RESOURCE_UNAVAILABLE,
626 INVALID_RESOURCE_ID,
627 spi);
628 }
Nathan Harold93962f32017-03-07 13:23:36 -0800629 spi =
ludi1a06aa72017-05-12 09:15:00 -0700630 mSrvConfig
631 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800632 .ipSecAllocateSpi(
633 resourceId,
634 direction,
635 localAddress,
636 remoteAddress,
637 requestedSpi);
638 Log.d(TAG, "Allocated SPI " + spi);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700639 mSpiRecords.put(
640 resourceId,
641 new SpiRecord(resourceId, binder, direction, localAddress, remoteAddress, spi));
Nathan Harold93962f32017-03-07 13:23:36 -0800642 } catch (ServiceSpecificException e) {
643 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700644 return new IpSecSpiResponse(
Nathan Harolda1afbd82017-04-24 16:16:34 -0700645 IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800646 } catch (RemoteException e) {
647 throw e.rethrowFromSystemServer();
648 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700649 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
650 }
651
652 /* This method should only be called from Binder threads. Do not call this from
653 * within the system server as it will crash the system on failure.
654 */
655 private synchronized <T extends ManagedResource> void releaseManagedResource(
656 ManagedResourceArray<T> resArray, int resourceId, String typeName)
657 throws RemoteException {
658 // We want to non-destructively get so that we can check credentials before removing
659 // this from the records.
660 T record = resArray.get(resourceId);
661
662 if (record == null) {
663 throw new IllegalArgumentException(
664 typeName + " " + resourceId + " is not available to be deleted");
665 }
666
667 record.release();
668 resArray.remove(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800669 }
670
671 /** Release a previously allocated SPI that has been registered with the system server */
672 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700673 public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
674 releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex");
675 }
676
677 /**
678 * This function finds and forcibly binds to a random system port, ensuring that the port cannot
679 * be unbound.
680 *
681 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
682 * a random open port and then bind by number, this function creates a temp socket, binds to a
683 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
684 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
685 * FileHandle.
686 *
687 * <p>The loop in this function handles the inherent race window between un-binding to a port
688 * and re-binding, during which the system could *technically* hand that port out to someone
689 * else.
690 */
691 private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
692 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
693 try {
694 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
695 Os.bind(probeSocket, INADDR_ANY, 0);
696 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
697 Os.close(probeSocket);
698 Log.v(TAG, "Binding to port " + port);
699 Os.bind(sockFd, INADDR_ANY, port);
700 return;
701 } catch (ErrnoException e) {
702 // Someone miraculously claimed the port just after we closed probeSocket.
703 if (e.errno == OsConstants.EADDRINUSE) {
704 continue;
705 }
706 throw e.rethrowAsIOException();
707 }
708 }
709 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
710 }
Nathan Harold93962f32017-03-07 13:23:36 -0800711
712 /**
713 * Open a socket via the system server and bind it to the specified port (random if port=0).
714 * This will return a PFD to the user that represent a bound UDP socket. The system server will
715 * cache the socket and a record of its owner so that it can and must be freed when no longer
716 * needed.
717 */
718 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700719 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
720 throws RemoteException {
721 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
722 throw new IllegalArgumentException(
723 "Specified port number must be a valid non-reserved UDP port");
724 }
725 int resourceId = mNextResourceId.getAndIncrement();
726 FileDescriptor sockFd = null;
727 try {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700728 if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).socket.isAvailable()) {
729 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
730 }
731
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700732 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
733
734 if (port != 0) {
735 Log.v(TAG, "Binding to port " + port);
736 Os.bind(sockFd, INADDR_ANY, port);
737 } else {
738 bindToRandomPort(sockFd);
739 }
740 // This code is common to both the unspecified and specified port cases
741 Os.setsockoptInt(
742 sockFd,
743 OsConstants.IPPROTO_UDP,
744 OsConstants.UDP_ENCAP,
745 OsConstants.UDP_ENCAP_ESPINUDP);
746
747 mUdpSocketRecords.put(
748 resourceId, new UdpSocketRecord(resourceId, binder, sockFd, port));
749 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
750 } catch (IOException | ErrnoException e) {
751 IoUtils.closeQuietly(sockFd);
752 }
753 // If we make it to here, then something has gone wrong and we couldn't open a socket.
754 // The only reasonable condition that would cause that is resource unavailable.
755 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -0800756 }
757
758 /** close a socket that has been been allocated by and registered with the system server */
759 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700760 public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
761
762 releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
763 }
Nathan Harold93962f32017-03-07 13:23:36 -0800764
765 /**
766 * Create a transport mode transform, which represent two security associations (one in each
767 * direction) in the kernel. The transform will be cached by the system server and must be freed
768 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
769 * that are using it, which will result in all of those sockets becoming unable to send or
770 * receive data.
771 */
772 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700773 public synchronized IpSecTransformResponse createTransportModeTransform(
774 IpSecConfig c, IBinder binder) throws RemoteException {
Nathan Harold93962f32017-03-07 13:23:36 -0800775 int resourceId = mNextResourceId.getAndIncrement();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700776 if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).transform.isAvailable()) {
777 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
778 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700779 SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
780 // TODO: Basic input validation here since it's coming over the Binder
781 int encapType, encapLocalPort = 0, encapRemotePort = 0;
782 UdpSocketRecord socketRecord = null;
783 encapType = c.getEncapType();
784 if (encapType != IpSecTransform.ENCAP_NONE) {
785 socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
786 encapLocalPort = socketRecord.getPort();
787 encapRemotePort = c.getEncapRemotePort();
788 }
789
Nathan Harold93962f32017-03-07 13:23:36 -0800790 for (int direction : DIRECTIONS) {
791 IpSecAlgorithm auth = c.getAuthentication(direction);
792 IpSecAlgorithm crypt = c.getEncryption(direction);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700793
794 spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
795 int spi = spis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800796 try {
ludi1a06aa72017-05-12 09:15:00 -0700797 mSrvConfig.getNetdInstance()
ludi0f807892017-05-20 14:15:09 -0700798 .ipSecAddSecurityAssociation(
799 resourceId,
800 c.getMode(),
801 direction,
802 (c.getLocalAddress() != null)
803 ? c.getLocalAddress().getHostAddress()
804 : "",
805 (c.getRemoteAddress() != null)
806 ? c.getRemoteAddress().getHostAddress()
807 : "",
808 (c.getNetwork() != null)
809 ? c.getNetwork().getNetworkHandle()
810 : 0,
811 spi,
812 (auth != null) ? auth.getName() : "",
813 (auth != null) ? auth.getKey() : null,
814 (auth != null) ? auth.getTruncationLengthBits() : 0,
815 (crypt != null) ? crypt.getName() : "",
816 (crypt != null) ? crypt.getKey() : null,
817 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
818 encapType,
819 encapLocalPort,
820 encapRemotePort);
Nathan Harold93962f32017-03-07 13:23:36 -0800821 } catch (ServiceSpecificException e) {
822 // FIXME: get the error code and throw is at an IOException from Errno Exception
ludi0f807892017-05-20 14:15:09 -0700823 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -0800824 }
825 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700826 // Both SAs were created successfully, time to construct a record and lock it away
827 mTransformRecords.put(
828 resourceId, new TransformRecord(resourceId, binder, c, spis, socketRecord));
829 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800830 }
831
832 /**
833 * Delete a transport mode transform that was previously allocated by + registered with the
834 * system server. If this is called on an inactive (or non-existent) transform, it will not
835 * return an error. It's safe to de-allocate transforms that may have already been deleted for
836 * other reasons.
837 */
838 @Override
839 public void deleteTransportModeTransform(int resourceId) throws RemoteException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700840 releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform");
Nathan Harold93962f32017-03-07 13:23:36 -0800841 }
842
843 /**
844 * Apply an active transport mode transform to a socket, which will apply the IPsec security
845 * association as a correspondent policy to the provided socket
846 */
847 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700848 public synchronized void applyTransportModeTransform(
849 ParcelFileDescriptor socket, int resourceId) throws RemoteException {
850 // Synchronize liberally here because we are using ManagedResources in this block
851 TransformRecord info;
852 // FIXME: this code should be factored out into a security check + getter
853 info = mTransformRecords.get(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800854
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700855 if (info == null) {
856 throw new IllegalArgumentException("Transform " + resourceId + " is not active");
857 }
Nathan Harold93962f32017-03-07 13:23:36 -0800858
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700859 // TODO: make this a function.
860 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
861 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
862 }
863
864 IpSecConfig c = info.getConfig();
865 try {
866 for (int direction : DIRECTIONS) {
ludi1a06aa72017-05-12 09:15:00 -0700867 mSrvConfig
868 .getNetdInstance()
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700869 .ipSecApplyTransportModeTransform(
870 socket.getFileDescriptor(),
871 resourceId,
872 direction,
873 (c.getLocalAddress() != null)
874 ? c.getLocalAddress().getHostAddress()
875 : "",
876 (c.getRemoteAddress() != null)
877 ? c.getRemoteAddress().getHostAddress()
878 : "",
879 info.getSpiRecord(direction).getSpi());
Nathan Harold93962f32017-03-07 13:23:36 -0800880 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700881 } catch (ServiceSpecificException e) {
882 // FIXME: get the error code and throw is at an IOException from Errno Exception
Nathan Harold93962f32017-03-07 13:23:36 -0800883 }
884 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700885
Nathan Harold93962f32017-03-07 13:23:36 -0800886 /**
887 * Remove a transport mode transform from a socket, applying the default (empty) policy. This
888 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
889 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
890 * used: reserved for future improved input validation.
891 */
892 @Override
893 public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
894 throws RemoteException {
895 try {
ludi1a06aa72017-05-12 09:15:00 -0700896 mSrvConfig
897 .getNetdInstance()
898 .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
Nathan Harold93962f32017-03-07 13:23:36 -0800899 } catch (ServiceSpecificException e) {
900 // FIXME: get the error code and throw is at an IOException from Errno Exception
901 }
902 }
903
904 @Override
ludib0c95b12017-05-22 10:52:23 -0700905 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800906 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
ludib0c95b12017-05-22 10:52:23 -0700907
908 pw.println("IpSecService dump:");
Nathan Harold1afbef42017-03-01 18:55:06 -0800909 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
910 pw.println();
ludib0c95b12017-05-22 10:52:23 -0700911
912 pw.println("mTransformRecords:");
913 pw.println(mTransformRecords);
914 pw.println("mUdpSocketRecords:");
915 pw.println(mUdpSocketRecords);
916 pw.println("mSpiRecords:");
917 pw.println(mSpiRecords);
Nathan Harold1afbef42017-03-01 18:55:06 -0800918 }
919}