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