blob: a7ce95ba0c881ffd8c45af382ec5d70a467f9b43 [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;
21import static android.net.IpSecManager.KEY_RESOURCE_ID;
22import static android.net.IpSecManager.KEY_SPI;
23import static android.net.IpSecManager.KEY_STATUS;
Nathan Harold1afbef42017-03-01 18:55:06 -080024
25import android.content.Context;
26import android.net.IIpSecService;
27import android.net.INetd;
Nathan Harold93962f32017-03-07 13:23:36 -080028import android.net.IpSecAlgorithm;
29import android.net.IpSecConfig;
30import android.net.IpSecManager;
31import android.net.IpSecTransform;
Nathan Harold1afbef42017-03-01 18:55:06 -080032import android.net.util.NetdService;
Nathan Harold93962f32017-03-07 13:23:36 -080033import android.os.Binder;
34import android.os.Bundle;
35import android.os.IBinder;
36import android.os.ParcelFileDescriptor;
Nathan Harold1afbef42017-03-01 18:55:06 -080037import android.os.RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -080038import android.os.ServiceSpecificException;
Nathan Harold1afbef42017-03-01 18:55:06 -080039import android.util.Log;
40import android.util.Slog;
Nathan Harold93962f32017-03-07 13:23:36 -080041import android.util.SparseArray;
42import com.android.internal.annotations.GuardedBy;
Nathan Harold1afbef42017-03-01 18:55:06 -080043import java.io.FileDescriptor;
44import java.io.PrintWriter;
Nathan Harold93962f32017-03-07 13:23:36 -080045import java.util.concurrent.atomic.AtomicInteger;
Nathan Harold1afbef42017-03-01 18:55:06 -080046
47/** @hide */
48public class IpSecService extends IIpSecService.Stub {
49 private static final String TAG = "IpSecService";
50 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
51 private static final String NETD_SERVICE_NAME = "netd";
Nathan Harold93962f32017-03-07 13:23:36 -080052 private static final int[] DIRECTIONS =
53 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
Nathan Harold1afbef42017-03-01 18:55:06 -080054
55 /** Binder context for this service */
56 private final Context mContext;
57
58 private Object mLock = new Object();
59
60 private static final int NETD_FETCH_TIMEOUT = 5000; //ms
61
Nathan Harold93962f32017-03-07 13:23:36 -080062 private AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
63
64 private abstract class ManagedResource implements IBinder.DeathRecipient {
65 final int pid;
66 final int uid;
67 private IBinder mBinder;
68
69 ManagedResource(IBinder binder) {
70 super();
71 mBinder = binder;
72 pid = Binder.getCallingPid();
73 uid = Binder.getCallingUid();
74
75 try {
76 mBinder.linkToDeath(this, 0);
77 } catch (RemoteException e) {
78 binderDied();
79 }
80 }
81
82 /**
83 * When this record is no longer needed for managing system resources this function should
84 * unlink all references held by the record to allow efficient garbage collection.
85 */
86 public final void release() {
87 //Release all the underlying system resources first
88 releaseResources();
89
90 if (mBinder != null) {
91 mBinder.unlinkToDeath(this, 0);
92 }
93 mBinder = null;
94
95 //remove this record so that it can be cleaned up
96 nullifyRecord();
97 }
98
99 /**
100 * If the Binder object dies, this function is called to free the system resources that are
101 * being managed by this record and to subsequently release this record for garbage
102 * collection
103 */
104 public final void binderDied() {
105 release();
106 }
107
108 /**
109 * Implement this method to release all object references contained in the subclass to allow
110 * efficient garbage collection of the record. This should remove any references to the
111 * record from all other locations that hold a reference as the record is no longer valid.
112 */
113 protected abstract void nullifyRecord();
114
115 /**
116 * Implement this method to release all system resources that are being protected by this
117 * record. Once the resources are released, the record should be invalidated and no longer
118 * used by calling releaseRecord()
119 */
120 protected abstract void releaseResources();
121 };
122
123 private final class TransformRecord extends ManagedResource {
124 private IpSecConfig mConfig;
125 private int mResourceId;
126
127 TransformRecord(IpSecConfig config, int resourceId, IBinder binder) {
128 super(binder);
129 mConfig = config;
130 mResourceId = resourceId;
131 }
132
133 public IpSecConfig getConfig() {
134 return mConfig;
135 }
136
137 @Override
138 protected void releaseResources() {
139 for (int direction : DIRECTIONS) {
140 try {
141 getNetdInstance()
142 .ipSecDeleteSecurityAssociation(
143 mResourceId,
144 direction,
145 (mConfig.getLocalAddress() != null)
146 ? mConfig.getLocalAddress().getHostAddress()
147 : "",
148 (mConfig.getRemoteAddress() != null)
149 ? mConfig.getRemoteAddress().getHostAddress()
150 : "",
151 mConfig.getSpi(direction));
152 } catch (ServiceSpecificException e) {
153 // FIXME: get the error code and throw is at an IOException from Errno Exception
154 } catch (RemoteException e) {
155 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
156 }
157 }
158 }
159
160 @Override
161 protected void nullifyRecord() {
162 mConfig = null;
163 mResourceId = INVALID_RESOURCE_ID;
164 }
165 }
166
167 private final class SpiRecord extends ManagedResource {
168 private final int mDirection;
169 private final String mLocalAddress;
170 private final String mRemoteAddress;
171 private final IBinder mBinder;
172 private int mSpi;
173 private int mResourceId;
174
175 SpiRecord(
176 int resourceId,
177 int direction,
178 String localAddress,
179 String remoteAddress,
180 int spi,
181 IBinder binder) {
182 super(binder);
183 mResourceId = resourceId;
184 mDirection = direction;
185 mLocalAddress = localAddress;
186 mRemoteAddress = remoteAddress;
187 mSpi = spi;
188 mBinder = binder;
189 }
190
191 protected void releaseResources() {
192 try {
193 getNetdInstance()
194 .ipSecDeleteSecurityAssociation(
195 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
196 } catch (ServiceSpecificException e) {
197 // FIXME: get the error code and throw is at an IOException from Errno Exception
198 } catch (RemoteException e) {
199 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
200 }
201 }
202
203 protected void nullifyRecord() {
204 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
205 mResourceId = INVALID_RESOURCE_ID;
206 }
207 }
208
209 @GuardedBy("mSpiRecords")
210 private final SparseArray<SpiRecord> mSpiRecords = new SparseArray<>();
211
212 @GuardedBy("mTransformRecords")
213 private final SparseArray<TransformRecord> mTransformRecords = new SparseArray<>();
214
Nathan Harold1afbef42017-03-01 18:55:06 -0800215 /**
216 * Constructs a new IpSecService instance
217 *
218 * @param context Binder context for this service
219 */
220 private IpSecService(Context context) {
221 mContext = context;
222 }
223
224 static IpSecService create(Context context) throws InterruptedException {
225 final IpSecService service = new IpSecService(context);
226 service.connectNativeNetdService();
227 return service;
228 }
229
230 public void systemReady() {
231 if (isNetdAlive()) {
232 Slog.d(TAG, "IpSecService is ready");
233 } else {
234 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
235 }
236 }
237
238 private void connectNativeNetdService() {
239 // Avoid blocking the system server to do this
240 Thread t =
241 new Thread(
242 new Runnable() {
243 @Override
244 public void run() {
245 synchronized (mLock) {
246 NetdService.get(NETD_FETCH_TIMEOUT);
247 }
248 }
249 });
250 t.run();
251 }
252
Nathan Harold93962f32017-03-07 13:23:36 -0800253 INetd getNetdInstance() throws RemoteException {
Nathan Harold1afbef42017-03-01 18:55:06 -0800254 final INetd netd = NetdService.getInstance();
255 if (netd == null) {
Nathan Harold93962f32017-03-07 13:23:36 -0800256 throw new RemoteException("Failed to Get Netd Instance");
Nathan Harold1afbef42017-03-01 18:55:06 -0800257 }
258 return netd;
259 }
260
261 boolean isNetdAlive() {
262 synchronized (mLock) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800263 try {
Nathan Harold93962f32017-03-07 13:23:36 -0800264 final INetd netd = getNetdInstance();
265 if (netd == null) {
266 return false;
267 }
Nathan Harold1afbef42017-03-01 18:55:06 -0800268 return netd.isAlive();
269 } catch (RemoteException re) {
270 return false;
271 }
272 }
273 }
274
275 @Override
Nathan Harold93962f32017-03-07 13:23:36 -0800276 /** Get a new SPI and maintain the reservation in the system server */
277 public Bundle reserveSecurityParameterIndex(
278 int direction, String remoteAddress, int requestedSpi, IBinder binder)
279 throws RemoteException {
280 int resourceId = mNextResourceId.getAndIncrement();
281
282 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
283 String localAddress = "";
284 Bundle retBundle = new Bundle(3);
285 try {
286 spi =
287 getNetdInstance()
288 .ipSecAllocateSpi(
289 resourceId,
290 direction,
291 localAddress,
292 remoteAddress,
293 requestedSpi);
294 Log.d(TAG, "Allocated SPI " + spi);
295 retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK);
296 retBundle.putInt(KEY_RESOURCE_ID, resourceId);
297 retBundle.putInt(KEY_SPI, spi);
298 synchronized (mSpiRecords) {
299 mSpiRecords.put(
300 resourceId,
301 new SpiRecord(
302 resourceId, direction, localAddress, remoteAddress, spi, binder));
303 }
304 } catch (ServiceSpecificException e) {
305 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
306 retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE);
307 retBundle.putInt(KEY_RESOURCE_ID, resourceId);
308 retBundle.putInt(KEY_SPI, spi);
309 } catch (RemoteException e) {
310 throw e.rethrowFromSystemServer();
311 }
312 return retBundle;
313 }
314
315 /** Release a previously allocated SPI that has been registered with the system server */
316 @Override
317 public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {}
318
319 /**
320 * Open a socket via the system server and bind it to the specified port (random if port=0).
321 * This will return a PFD to the user that represent a bound UDP socket. The system server will
322 * cache the socket and a record of its owner so that it can and must be freed when no longer
323 * needed.
324 */
325 @Override
326 public Bundle openUdpEncapsulationSocket(int port, IBinder binder) throws RemoteException {
327 return null;
328 }
329
330 /** close a socket that has been been allocated by and registered with the system server */
331 @Override
332 public void closeUdpEncapsulationSocket(ParcelFileDescriptor socket) {}
333
334 /**
335 * Create a transport mode transform, which represent two security associations (one in each
336 * direction) in the kernel. The transform will be cached by the system server and must be freed
337 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
338 * that are using it, which will result in all of those sockets becoming unable to send or
339 * receive data.
340 */
341 @Override
342 public Bundle createTransportModeTransform(IpSecConfig c, IBinder binder)
343 throws RemoteException {
344 // TODO: Basic input validation here since it's coming over the Binder
345 int resourceId = mNextResourceId.getAndIncrement();
346 for (int direction : DIRECTIONS) {
347 IpSecAlgorithm auth = c.getAuthentication(direction);
348 IpSecAlgorithm crypt = c.getEncryption(direction);
349 try {
350 int result =
351 getNetdInstance()
352 .ipSecAddSecurityAssociation(
353 resourceId,
354 c.getMode(),
355 direction,
356 (c.getLocalAddress() != null)
357 ? c.getLocalAddress().getHostAddress()
358 : "",
359 (c.getRemoteAddress() != null)
360 ? c.getRemoteAddress().getHostAddress()
361 : "",
362 (c.getNetwork() != null)
363 ? c.getNetwork().getNetworkHandle()
364 : 0,
365 c.getSpi(direction),
366 (auth != null) ? auth.getName() : "",
367 (auth != null) ? auth.getKey() : null,
368 (auth != null) ? auth.getTruncationLengthBits() : 0,
369 (crypt != null) ? crypt.getName() : "",
370 (crypt != null) ? crypt.getKey() : null,
371 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
372 c.getEncapType(),
373 c.getEncapLocalPort(),
374 c.getEncapRemotePort());
375 if (result != c.getSpi(direction)) {
376 // TODO: cleanup the first SA if creation of second SA fails
377 Bundle retBundle = new Bundle(2);
378 retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE);
379 retBundle.putInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
380 return retBundle;
381 }
382 } catch (ServiceSpecificException e) {
383 // FIXME: get the error code and throw is at an IOException from Errno Exception
384 }
385 }
386 synchronized (mTransformRecords) {
387 mTransformRecords.put(resourceId, new TransformRecord(c, resourceId, binder));
388 }
389
390 Bundle retBundle = new Bundle(2);
391 retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK);
392 retBundle.putInt(KEY_RESOURCE_ID, resourceId);
393 return retBundle;
394 }
395
396 /**
397 * Delete a transport mode transform that was previously allocated by + registered with the
398 * system server. If this is called on an inactive (or non-existent) transform, it will not
399 * return an error. It's safe to de-allocate transforms that may have already been deleted for
400 * other reasons.
401 */
402 @Override
403 public void deleteTransportModeTransform(int resourceId) throws RemoteException {
404 synchronized (mTransformRecords) {
405 TransformRecord record;
406 // We want to non-destructively get so that we can check credentials before removing
407 // this from the records.
408 record = mTransformRecords.get(resourceId);
409
410 if (record == null) {
411 throw new IllegalArgumentException(
412 "Transform " + resourceId + " is not available to be deleted");
413 }
414
415 if (record.pid != Binder.getCallingPid() || record.uid != Binder.getCallingUid()) {
416 throw new SecurityException("Only the owner of an IpSec Transform may delete it!");
417 }
418
419 // TODO: if releaseResources() throws RemoteException, we can try again to clean up on
420 // binder death. Need to make sure that path is actually functional.
421 record.releaseResources();
422 mTransformRecords.remove(resourceId);
423 record.nullifyRecord();
424 }
425 }
426
427 /**
428 * Apply an active transport mode transform to a socket, which will apply the IPsec security
429 * association as a correspondent policy to the provided socket
430 */
431 @Override
432 public void applyTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
433 throws RemoteException {
434
435 synchronized (mTransformRecords) {
436 TransformRecord info;
437 // FIXME: this code should be factored out into a security check + getter
438 info = mTransformRecords.get(resourceId);
439
440 if (info == null) {
441 throw new IllegalArgumentException("Transform " + resourceId + " is not active");
442 }
443
444 // TODO: make this a function.
445 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
446 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
447 }
448
449 IpSecConfig c = info.getConfig();
450 try {
451 for (int direction : DIRECTIONS) {
452 getNetdInstance()
453 .ipSecApplyTransportModeTransform(
454 socket.getFileDescriptor(),
455 resourceId,
456 direction,
457 (c.getLocalAddress() != null)
458 ? c.getLocalAddress().getHostAddress()
459 : "",
460 (c.getRemoteAddress() != null)
461 ? c.getRemoteAddress().getHostAddress()
462 : "",
463 c.getSpi(direction));
464 }
465 } catch (ServiceSpecificException e) {
466 // FIXME: get the error code and throw is at an IOException from Errno Exception
467 }
468 }
469 }
470 /**
471 * Remove a transport mode transform from a socket, applying the default (empty) policy. This
472 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
473 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
474 * used: reserved for future improved input validation.
475 */
476 @Override
477 public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
478 throws RemoteException {
479 try {
480 getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
481 } catch (ServiceSpecificException e) {
482 // FIXME: get the error code and throw is at an IOException from Errno Exception
483 }
484 }
485
486 @Override
Nathan Harold1afbef42017-03-01 18:55:06 -0800487 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
488 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
489
490 pw.println("IpSecService Log:");
491 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
492 pw.println();
493 }
494}