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 
17 package com.android.server;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNull;
21 import static org.junit.Assert.fail;
22 import static org.mockito.Matchers.anyInt;
23 import static org.mockito.Matchers.anyObject;
24 import static org.mockito.Matchers.eq;
25 import static org.mockito.Mockito.doThrow;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.spy;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 
31 import android.content.Context;
32 import android.os.Binder;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 
36 import androidx.test.filters.SmallTest;
37 import androidx.test.runner.AndroidJUnit4;
38 
39 import com.android.server.IpSecService.IResource;
40 import com.android.server.IpSecService.RefcountedResource;
41 
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 
46 import java.util.ArrayList;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Set;
50 import java.util.concurrent.ThreadLocalRandom;
51 
52 /** Unit tests for {@link IpSecService.RefcountedResource}. */
53 @SmallTest
54 @RunWith(AndroidJUnit4.class)
55 public class IpSecServiceRefcountedResourceTest {
56     Context mMockContext;
57     IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
58     IpSecService mIpSecService;
59 
60     @Before
setUp()61     public void setUp() throws Exception {
62         mMockContext = mock(Context.class);
63         mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
64         mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
65     }
66 
assertResourceState( RefcountedResource<IResource> resource, int refCount, int userReleaseCallCount, int releaseReferenceCallCount, int invalidateCallCount, int freeUnderlyingResourcesCallCount)67     private void assertResourceState(
68             RefcountedResource<IResource> resource,
69             int refCount,
70             int userReleaseCallCount,
71             int releaseReferenceCallCount,
72             int invalidateCallCount,
73             int freeUnderlyingResourcesCallCount)
74             throws RemoteException {
75         // Check refcount on RefcountedResource
76         assertEquals(refCount, resource.mRefCount);
77 
78         // Check call count of RefcountedResource
79         verify(resource, times(userReleaseCallCount)).userRelease();
80         verify(resource, times(releaseReferenceCallCount)).releaseReference();
81 
82         // Check call count of IResource
83         verify(resource.getResource(), times(invalidateCallCount)).invalidate();
84         verify(resource.getResource(), times(freeUnderlyingResourcesCallCount))
85                 .freeUnderlyingResources();
86     }
87 
88     /** Adds mockito instrumentation */
getTestRefcountedResource( RefcountedResource... children)89     private RefcountedResource<IResource> getTestRefcountedResource(
90             RefcountedResource... children) {
91         return getTestRefcountedResource(new Binder(), children);
92     }
93 
94     /** Adds mockito instrumentation with provided binder */
getTestRefcountedResource( IBinder binder, RefcountedResource... children)95     private RefcountedResource<IResource> getTestRefcountedResource(
96             IBinder binder, RefcountedResource... children) {
97         return spy(
98                 mIpSecService
99                 .new RefcountedResource<IResource>(mock(IResource.class), binder, children));
100     }
101 
102     @Test
testConstructor()103     public void testConstructor() throws RemoteException {
104         IBinder binderMock = mock(IBinder.class);
105         RefcountedResource<IResource> resource = getTestRefcountedResource(binderMock);
106 
107         // Verify resource's refcount starts at 1 (for user-reference)
108         assertResourceState(resource, 1, 0, 0, 0, 0);
109 
110         // Verify linking to binder death
111         verify(binderMock).linkToDeath(anyObject(), anyInt());
112     }
113 
114     @Test
testConstructorWithChildren()115     public void testConstructorWithChildren() throws RemoteException {
116         IBinder binderMockChild = mock(IBinder.class);
117         IBinder binderMockParent = mock(IBinder.class);
118         RefcountedResource<IResource> childResource = getTestRefcountedResource(binderMockChild);
119         RefcountedResource<IResource> parentResource =
120                 getTestRefcountedResource(binderMockParent, childResource);
121 
122         // Verify parent's refcount starts at 1 (for user-reference)
123         assertResourceState(parentResource, 1, 0, 0, 0, 0);
124 
125         // Verify child's refcounts were incremented
126         assertResourceState(childResource, 2, 0, 0, 0, 0);
127 
128         // Verify linking to binder death
129         verify(binderMockChild).linkToDeath(anyObject(), anyInt());
130         verify(binderMockParent).linkToDeath(anyObject(), anyInt());
131     }
132 
133     @Test
testFailLinkToDeath()134     public void testFailLinkToDeath() throws RemoteException {
135         IBinder binderMock = mock(IBinder.class);
136         doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt());
137 
138         try {
139             getTestRefcountedResource(binderMock);
140             fail("Expected exception to propogate when binder fails to link to death");
141         } catch (RuntimeException expected) {
142         }
143     }
144 
145     @Test
testCleanupAndRelease()146     public void testCleanupAndRelease() throws RemoteException {
147         IBinder binderMock = mock(IBinder.class);
148         RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
149 
150         // Verify user-initiated cleanup path decrements refcount and calls full cleanup flow
151         refcountedResource.userRelease();
152         assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
153 
154         // Verify user-initated cleanup path unlinks from binder
155         verify(binderMock).unlinkToDeath(eq(refcountedResource), eq(0));
156         assertNull(refcountedResource.mBinder);
157     }
158 
159     @Test
testMultipleCallsToCleanupAndRelease()160     public void testMultipleCallsToCleanupAndRelease() throws RemoteException {
161         RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
162 
163         // Verify calling userRelease multiple times does not trigger any other cleanup
164         // methods
165         refcountedResource.userRelease();
166         assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
167 
168         refcountedResource.userRelease();
169         refcountedResource.userRelease();
170         assertResourceState(refcountedResource, -1, 3, 1, 1, 1);
171     }
172 
173     @Test
testBinderDeathAfterCleanupAndReleaseDoesNothing()174     public void testBinderDeathAfterCleanupAndReleaseDoesNothing() throws RemoteException {
175         RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
176 
177         refcountedResource.userRelease();
178         assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
179 
180         // Verify binder death call does not trigger any other cleanup methods if called after
181         // userRelease()
182         refcountedResource.binderDied();
183         assertResourceState(refcountedResource, -1, 2, 1, 1, 1);
184     }
185 
186     @Test
testBinderDeath()187     public void testBinderDeath() throws RemoteException {
188         RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
189 
190         // Verify binder death caused cleanup
191         refcountedResource.binderDied();
192         verify(refcountedResource, times(1)).binderDied();
193         assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
194         assertNull(refcountedResource.mBinder);
195     }
196 
197     @Test
testCleanupParentDecrementsChildRefcount()198     public void testCleanupParentDecrementsChildRefcount() throws RemoteException {
199         RefcountedResource<IResource> childResource = getTestRefcountedResource();
200         RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
201 
202         parentResource.userRelease();
203 
204         // Verify parent gets cleaned up properly, and triggers releaseReference on
205         // child
206         assertResourceState(childResource, 1, 0, 1, 0, 0);
207         assertResourceState(parentResource, -1, 1, 1, 1, 1);
208     }
209 
210     @Test
testCleanupReferencedChildDoesNotTriggerRelease()211     public void testCleanupReferencedChildDoesNotTriggerRelease() throws RemoteException {
212         RefcountedResource<IResource> childResource = getTestRefcountedResource();
213         RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
214 
215         childResource.userRelease();
216 
217         // Verify that child does not clean up kernel resources and quota.
218         assertResourceState(childResource, 1, 1, 1, 1, 0);
219         assertResourceState(parentResource, 1, 0, 0, 0, 0);
220     }
221 
222     @Test
testTwoParents()223     public void testTwoParents() throws RemoteException {
224         RefcountedResource<IResource> childResource = getTestRefcountedResource();
225         RefcountedResource<IResource> parentResource1 = getTestRefcountedResource(childResource);
226         RefcountedResource<IResource> parentResource2 = getTestRefcountedResource(childResource);
227 
228         // Verify that child does not cleanup kernel resources and quota until all references
229         // have been released. Assumption: parents release correctly based on
230         // testCleanupParentDecrementsChildRefcount()
231         childResource.userRelease();
232         assertResourceState(childResource, 2, 1, 1, 1, 0);
233 
234         parentResource1.userRelease();
235         assertResourceState(childResource, 1, 1, 2, 1, 0);
236 
237         parentResource2.userRelease();
238         assertResourceState(childResource, -1, 1, 3, 1, 1);
239     }
240 
241     @Test
testTwoChildren()242     public void testTwoChildren() throws RemoteException {
243         RefcountedResource<IResource> childResource1 = getTestRefcountedResource();
244         RefcountedResource<IResource> childResource2 = getTestRefcountedResource();
245         RefcountedResource<IResource> parentResource =
246                 getTestRefcountedResource(childResource1, childResource2);
247 
248         childResource1.userRelease();
249         assertResourceState(childResource1, 1, 1, 1, 1, 0);
250         assertResourceState(childResource2, 2, 0, 0, 0, 0);
251 
252         parentResource.userRelease();
253         assertResourceState(childResource1, -1, 1, 2, 1, 1);
254         assertResourceState(childResource2, 1, 0, 1, 0, 0);
255 
256         childResource2.userRelease();
257         assertResourceState(childResource1, -1, 1, 2, 1, 1);
258         assertResourceState(childResource2, -1, 1, 2, 1, 1);
259     }
260 
261     @Test
testSampleUdpEncapTranform()262     public void testSampleUdpEncapTranform() throws RemoteException {
263         RefcountedResource<IResource> spi1 = getTestRefcountedResource();
264         RefcountedResource<IResource> spi2 = getTestRefcountedResource();
265         RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
266         RefcountedResource<IResource> transform =
267                 getTestRefcountedResource(spi1, spi2, udpEncapSocket);
268 
269         // Pretend one SPI goes out of reference (releaseManagedResource -> userRelease)
270         spi1.userRelease();
271 
272         // User called releaseManagedResource on udpEncap socket
273         udpEncapSocket.userRelease();
274 
275         // User dies, and binder kills the rest
276         spi2.binderDied();
277         transform.binderDied();
278 
279         // Check resource states
280         assertResourceState(spi1, -1, 1, 2, 1, 1);
281         assertResourceState(spi2, -1, 1, 2, 1, 1);
282         assertResourceState(udpEncapSocket, -1, 1, 2, 1, 1);
283         assertResourceState(transform, -1, 1, 1, 1, 1);
284     }
285 
286     @Test
testSampleDualTransformEncapSocket()287     public void testSampleDualTransformEncapSocket() throws RemoteException {
288         RefcountedResource<IResource> spi1 = getTestRefcountedResource();
289         RefcountedResource<IResource> spi2 = getTestRefcountedResource();
290         RefcountedResource<IResource> spi3 = getTestRefcountedResource();
291         RefcountedResource<IResource> spi4 = getTestRefcountedResource();
292         RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
293         RefcountedResource<IResource> transform1 =
294                 getTestRefcountedResource(spi1, spi2, udpEncapSocket);
295         RefcountedResource<IResource> transform2 =
296                 getTestRefcountedResource(spi3, spi4, udpEncapSocket);
297 
298         // Pretend one SPIs goes out of reference (releaseManagedResource -> userRelease)
299         spi1.userRelease();
300 
301         // User called releaseManagedResource on udpEncap socket and spi4
302         udpEncapSocket.userRelease();
303         spi4.userRelease();
304 
305         // User dies, and binder kills the rest
306         spi2.binderDied();
307         spi3.binderDied();
308         transform2.binderDied();
309         transform1.binderDied();
310 
311         // Check resource states
312         assertResourceState(spi1, -1, 1, 2, 1, 1);
313         assertResourceState(spi2, -1, 1, 2, 1, 1);
314         assertResourceState(spi3, -1, 1, 2, 1, 1);
315         assertResourceState(spi4, -1, 1, 2, 1, 1);
316         assertResourceState(udpEncapSocket, -1, 1, 3, 1, 1);
317         assertResourceState(transform1, -1, 1, 1, 1, 1);
318         assertResourceState(transform2, -1, 1, 1, 1, 1);
319     }
320 
321     @Test
fuzzTest()322     public void fuzzTest() throws RemoteException {
323         List<RefcountedResource<IResource>> resources = new ArrayList<>();
324 
325         // Build a tree of resources
326         for (int i = 0; i < 100; i++) {
327             // Choose a random number of children from the existing list
328             int numChildren = ThreadLocalRandom.current().nextInt(0, resources.size() + 1);
329 
330             // Build a (random) list of children
331             Set<RefcountedResource<IResource>> children = new HashSet<>();
332             for (int j = 0; j < numChildren; j++) {
333                 int childIndex = ThreadLocalRandom.current().nextInt(0, resources.size());
334                 children.add(resources.get(childIndex));
335             }
336 
337             RefcountedResource<IResource> newRefcountedResource =
338                     getTestRefcountedResource(
339                             children.toArray(new RefcountedResource[children.size()]));
340             resources.add(newRefcountedResource);
341         }
342 
343         // Cleanup all resources in a random order
344         List<RefcountedResource<IResource>> clonedResources =
345                 new ArrayList<>(resources); // shallow copy
346         while (!clonedResources.isEmpty()) {
347             int index = ThreadLocalRandom.current().nextInt(0, clonedResources.size());
348             RefcountedResource<IResource> refcountedResource = clonedResources.get(index);
349             refcountedResource.userRelease();
350             clonedResources.remove(index);
351         }
352 
353         // Verify all resources were cleaned up properly
354         for (RefcountedResource<IResource> refcountedResource : resources) {
355             assertEquals(-1, refcountedResource.mRefCount);
356         }
357     }
358 }
359