1/* 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; 20import 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; 24 25import android.content.Context; 26import android.net.IIpSecService; 27import android.net.INetd; 28import android.net.IpSecAlgorithm; 29import android.net.IpSecConfig; 30import android.net.IpSecManager; 31import android.net.IpSecTransform; 32import android.net.util.NetdService; 33import android.os.Binder; 34import android.os.Bundle; 35import android.os.IBinder; 36import android.os.ParcelFileDescriptor; 37import android.os.RemoteException; 38import android.os.ServiceSpecificException; 39import android.util.Log; 40import android.util.Slog; 41import android.util.SparseArray; 42import com.android.internal.annotations.GuardedBy; 43import java.io.FileDescriptor; 44import java.io.PrintWriter; 45import java.util.concurrent.atomic.AtomicInteger; 46 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"; 52 private static final int[] DIRECTIONS = 53 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}; 54 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 62 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 215 /** 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 253 INetd getNetdInstance() throws RemoteException { 254 final INetd netd = NetdService.getInstance(); 255 if (netd == null) { 256 throw new RemoteException("Failed to Get Netd Instance"); 257 } 258 return netd; 259 } 260 261 boolean isNetdAlive() { 262 synchronized (mLock) { 263 try { 264 final INetd netd = getNetdInstance(); 265 if (netd == null) { 266 return false; 267 } 268 return netd.isAlive(); 269 } catch (RemoteException re) { 270 return false; 271 } 272 } 273 } 274 275 @Override 276 /** 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 487 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} 495