• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.content.pm.PackageManager.PERMISSION_DENIED;
20 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
21 import static android.net.INetd.IF_STATE_DOWN;
22 import static android.net.INetd.IF_STATE_UP;
23 import static android.net.IpSecManager.DIRECTION_FWD;
24 import static android.net.IpSecManager.DIRECTION_IN;
25 import static android.net.IpSecManager.DIRECTION_OUT;
26 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
27 import static android.system.OsConstants.AF_INET;
28 import static android.system.OsConstants.AF_INET6;
29 
30 import static org.junit.Assert.assertEquals;
31 import static org.junit.Assert.assertNotNull;
32 import static org.junit.Assert.fail;
33 import static org.mockito.ArgumentMatchers.argThat;
34 import static org.mockito.Matchers.anyInt;
35 import static org.mockito.Matchers.anyString;
36 import static org.mockito.Matchers.eq;
37 import static org.mockito.Mockito.mock;
38 import static org.mockito.Mockito.times;
39 import static org.mockito.Mockito.verify;
40 import static org.mockito.Mockito.when;
41 
42 import android.app.AppOpsManager;
43 import android.content.Context;
44 import android.content.pm.PackageManager;
45 import android.net.ConnectivityManager;
46 import android.net.INetd;
47 import android.net.InetAddresses;
48 import android.net.InterfaceConfigurationParcel;
49 import android.net.IpSecAlgorithm;
50 import android.net.IpSecConfig;
51 import android.net.IpSecManager;
52 import android.net.IpSecSpiResponse;
53 import android.net.IpSecTransform;
54 import android.net.IpSecTransformResponse;
55 import android.net.IpSecTunnelInterfaceResponse;
56 import android.net.IpSecUdpEncapResponse;
57 import android.net.LinkAddress;
58 import android.net.LinkProperties;
59 import android.net.Network;
60 import android.os.Binder;
61 import android.os.Build;
62 import android.os.ParcelFileDescriptor;
63 import android.os.RemoteException;
64 import android.system.Os;
65 import android.test.mock.MockContext;
66 import android.util.ArraySet;
67 
68 import androidx.test.filters.SmallTest;
69 
70 import com.android.server.IpSecService.TunnelInterfaceRecord;
71 import com.android.testutils.DevSdkIgnoreRule;
72 
73 import org.junit.Before;
74 import org.junit.Ignore;
75 import org.junit.Rule;
76 import org.junit.Test;
77 import org.junit.runner.RunWith;
78 import org.junit.runners.Parameterized;
79 
80 import java.net.Inet4Address;
81 import java.net.Socket;
82 import java.util.Arrays;
83 import java.util.Collection;
84 import java.util.Set;
85 
86 /** Unit tests for {@link IpSecService}. */
87 @SmallTest
88 @RunWith(Parameterized.class)
89 public class IpSecServiceParameterizedTest {
90     @Rule
91     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(
92             Build.VERSION_CODES.R /* ignoreClassUpTo */);
93 
94     private static final int TEST_SPI = 0xD1201D;
95 
96     private final String mSourceAddr;
97     private final String mDestinationAddr;
98     private final LinkAddress mLocalInnerAddress;
99     private final int mFamily;
100 
101     private static final int[] ADDRESS_FAMILIES =
102             new int[] {AF_INET, AF_INET6};
103 
104     @Parameterized.Parameters
ipSecConfigs()105     public static Collection ipSecConfigs() {
106         return Arrays.asList(
107                 new Object[][] {
108                 {"1.2.3.4", "8.8.4.4", "10.0.1.1/24", AF_INET},
109                 {"2601::2", "2601::10", "2001:db8::1/64", AF_INET6}
110         });
111     }
112 
113     private static final byte[] AEAD_KEY = {
114         0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
115         0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
116         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
117         0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
118         0x73, 0x61, 0x6C, 0x74
119     };
120     private static final byte[] CRYPT_KEY = {
121         0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
122         0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
123         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
124         0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
125     };
126     private static final byte[] AUTH_KEY = {
127         0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
128         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
129         0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
130         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
131     };
132 
133     AppOpsManager mMockAppOps = mock(AppOpsManager.class);
134     ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);
135 
136     TestContext mTestContext = new TestContext();
137 
138     private class TestContext extends MockContext {
139         private Set<String> mAllowedPermissions = new ArraySet<>(Arrays.asList(
140                 android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
141                 android.Manifest.permission.NETWORK_STACK,
142                 PERMISSION_MAINLINE_NETWORK_STACK));
143 
setAllowedPermissions(String... permissions)144         private void setAllowedPermissions(String... permissions) {
145             mAllowedPermissions = new ArraySet<>(permissions);
146         }
147 
148         @Override
getSystemService(String name)149         public Object getSystemService(String name) {
150             switch(name) {
151                 case Context.APP_OPS_SERVICE:
152                     return mMockAppOps;
153                 case Context.CONNECTIVITY_SERVICE:
154                     return mMockConnectivityMgr;
155                 default:
156                     return null;
157             }
158         }
159 
160         @Override
getSystemServiceName(Class<?> serviceClass)161         public String getSystemServiceName(Class<?> serviceClass) {
162             if (ConnectivityManager.class == serviceClass) {
163                 return Context.CONNECTIVITY_SERVICE;
164             }
165             return null;
166         }
167 
168         @Override
getPackageManager()169         public PackageManager getPackageManager() {
170             return mMockPkgMgr;
171         }
172 
173         @Override
enforceCallingOrSelfPermission(String permission, String message)174         public void enforceCallingOrSelfPermission(String permission, String message) {
175             if (mAllowedPermissions.contains(permission)) {
176                 return;
177             } else {
178                 throw new SecurityException("Unavailable permission requested");
179             }
180         }
181 
182         @Override
checkCallingOrSelfPermission(String permission)183         public int checkCallingOrSelfPermission(String permission) {
184             if (mAllowedPermissions.contains(permission)) {
185                 return PERMISSION_GRANTED;
186             } else {
187                 return PERMISSION_DENIED;
188             }
189         }
190     }
191 
makeDependencies()192     private IpSecService.Dependencies makeDependencies() throws RemoteException {
193         final IpSecService.Dependencies deps = mock(IpSecService.Dependencies.class);
194         when(deps.getNetdInstance(mTestContext)).thenReturn(mMockNetd);
195         return deps;
196     }
197 
198     INetd mMockNetd;
199     PackageManager mMockPkgMgr;
200     IpSecService.Dependencies mDeps;
201     IpSecService mIpSecService;
202     Network fakeNetwork = new Network(0xAB);
203     int mUid = Os.getuid();
204 
205     private static final IpSecAlgorithm AUTH_ALGO =
206             new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4);
207     private static final IpSecAlgorithm CRYPT_ALGO =
208             new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
209     private static final IpSecAlgorithm AEAD_ALGO =
210             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
211     private static final int REMOTE_ENCAP_PORT = 4500;
212 
213     private static final String BLESSED_PACKAGE = "blessedPackage";
214     private static final String SYSTEM_PACKAGE = "systemPackage";
215     private static final String BAD_PACKAGE = "badPackage";
216 
IpSecServiceParameterizedTest( String sourceAddr, String destAddr, String localInnerAddr, int family)217     public IpSecServiceParameterizedTest(
218             String sourceAddr, String destAddr, String localInnerAddr, int family) {
219         mSourceAddr = sourceAddr;
220         mDestinationAddr = destAddr;
221         mLocalInnerAddress = new LinkAddress(localInnerAddr);
222         mFamily = family;
223     }
224 
225     @Before
setUp()226     public void setUp() throws Exception {
227         mMockNetd = mock(INetd.class);
228         mMockPkgMgr = mock(PackageManager.class);
229         mDeps = makeDependencies();
230         mIpSecService = new IpSecService(mTestContext, mDeps);
231 
232         // PackageManager should always return true (feature flag tests in IpSecServiceTest)
233         when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
234 
235         // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED.
236         when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE)))
237                 .thenReturn(AppOpsManager.MODE_ALLOWED);
238         // A system package will not be granted the app op, so this should fall back to
239         // a permissions check, which should pass.
240         when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE)))
241                 .thenReturn(AppOpsManager.MODE_DEFAULT);
242         // A mismatch between the package name and the UID will return MODE_IGNORED.
243         when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE)))
244                 .thenReturn(AppOpsManager.MODE_IGNORED);
245     }
246 
247     //TODO: Add a test to verify SPI.
248 
249     @Test
testIpSecServiceReserveSpi()250     public void testIpSecServiceReserveSpi() throws Exception {
251         when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
252                 .thenReturn(TEST_SPI);
253 
254         IpSecSpiResponse spiResp =
255                 mIpSecService.allocateSecurityParameterIndex(
256                         mDestinationAddr, TEST_SPI, new Binder());
257         assertEquals(IpSecManager.Status.OK, spiResp.status);
258         assertEquals(TEST_SPI, spiResp.spi);
259     }
260 
261     @Test
testReleaseSecurityParameterIndex()262     public void testReleaseSecurityParameterIndex() throws Exception {
263         when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
264                 .thenReturn(TEST_SPI);
265 
266         IpSecSpiResponse spiResp =
267                 mIpSecService.allocateSecurityParameterIndex(
268                         mDestinationAddr, TEST_SPI, new Binder());
269 
270         mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
271 
272         verify(mMockNetd)
273                 .ipSecDeleteSecurityAssociation(
274                         eq(mUid),
275                         anyString(),
276                         anyString(),
277                         eq(TEST_SPI),
278                         anyInt(),
279                         anyInt(),
280                         anyInt());
281 
282         // Verify quota and RefcountedResource objects cleaned up
283         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
284         assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
285         try {
286             userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId);
287             fail("Expected IllegalArgumentException on attempt to access deleted resource");
288         } catch (IllegalArgumentException expected) {
289 
290         }
291     }
292 
293     @Test
testSecurityParameterIndexBinderDeath()294     public void testSecurityParameterIndexBinderDeath() throws Exception {
295         when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
296                 .thenReturn(TEST_SPI);
297 
298         IpSecSpiResponse spiResp =
299                 mIpSecService.allocateSecurityParameterIndex(
300                         mDestinationAddr, TEST_SPI, new Binder());
301 
302         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
303         IpSecService.RefcountedResource refcountedRecord =
304                 userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId);
305 
306         refcountedRecord.binderDied();
307 
308         verify(mMockNetd)
309                 .ipSecDeleteSecurityAssociation(
310                         eq(mUid),
311                         anyString(),
312                         anyString(),
313                         eq(TEST_SPI),
314                         anyInt(),
315                         anyInt(),
316                         anyInt());
317 
318         // Verify quota and RefcountedResource objects cleaned up
319         assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
320         try {
321             userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId);
322             fail("Expected IllegalArgumentException on attempt to access deleted resource");
323         } catch (IllegalArgumentException expected) {
324 
325         }
326     }
327 
getNewSpiResourceId(String remoteAddress, int returnSpi)328     private int getNewSpiResourceId(String remoteAddress, int returnSpi) throws Exception {
329         when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), anyString(), anyInt()))
330                 .thenReturn(returnSpi);
331 
332         IpSecSpiResponse spi =
333                 mIpSecService.allocateSecurityParameterIndex(
334                         InetAddresses.parseNumericAddress(remoteAddress).getHostAddress(),
335                         IpSecManager.INVALID_SECURITY_PARAMETER_INDEX,
336                         new Binder());
337         return spi.resourceId;
338     }
339 
addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config)340     private void addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config) throws Exception {
341         config.setSpiResourceId(getNewSpiResourceId(mDestinationAddr, TEST_SPI));
342         config.setSourceAddress(mSourceAddr);
343         config.setDestinationAddress(mDestinationAddr);
344     }
345 
addAuthAndCryptToIpSecConfig(IpSecConfig config)346     private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception {
347         config.setEncryption(CRYPT_ALGO);
348         config.setAuthentication(AUTH_ALGO);
349     }
350 
addEncapSocketToIpSecConfig(int resourceId, IpSecConfig config)351     private void addEncapSocketToIpSecConfig(int resourceId, IpSecConfig config) throws Exception {
352         config.setEncapType(IpSecTransform.ENCAP_ESPINUDP);
353         config.setEncapSocketResourceId(resourceId);
354         config.setEncapRemotePort(REMOTE_ENCAP_PORT);
355     }
356 
verifyTransformNetdCalledForCreatingSA( IpSecConfig config, IpSecTransformResponse resp)357     private void verifyTransformNetdCalledForCreatingSA(
358             IpSecConfig config, IpSecTransformResponse resp) throws Exception {
359         verifyTransformNetdCalledForCreatingSA(config, resp, 0);
360     }
361 
verifyTransformNetdCalledForCreatingSA( IpSecConfig config, IpSecTransformResponse resp, int encapSocketPort)362     private void verifyTransformNetdCalledForCreatingSA(
363             IpSecConfig config, IpSecTransformResponse resp, int encapSocketPort) throws Exception {
364         IpSecAlgorithm auth = config.getAuthentication();
365         IpSecAlgorithm crypt = config.getEncryption();
366         IpSecAlgorithm authCrypt = config.getAuthenticatedEncryption();
367 
368         verify(mMockNetd, times(1))
369                 .ipSecAddSecurityAssociation(
370                         eq(mUid),
371                         eq(config.getMode()),
372                         eq(config.getSourceAddress()),
373                         eq(config.getDestinationAddress()),
374                         eq((config.getNetwork() != null) ? config.getNetwork().netId : 0),
375                         eq(TEST_SPI),
376                         eq(0),
377                         eq(0),
378                         eq((auth != null) ? auth.getName() : ""),
379                         eq((auth != null) ? auth.getKey() : new byte[] {}),
380                         eq((auth != null) ? auth.getTruncationLengthBits() : 0),
381                         eq((crypt != null) ? crypt.getName() : ""),
382                         eq((crypt != null) ? crypt.getKey() : new byte[] {}),
383                         eq((crypt != null) ? crypt.getTruncationLengthBits() : 0),
384                         eq((authCrypt != null) ? authCrypt.getName() : ""),
385                         eq((authCrypt != null) ? authCrypt.getKey() : new byte[] {}),
386                         eq((authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0),
387                         eq(config.getEncapType()),
388                         eq(encapSocketPort),
389                         eq(config.getEncapRemotePort()),
390                         eq(config.getXfrmInterfaceId()));
391     }
392 
393     @Test
testCreateTransform()394     public void testCreateTransform() throws Exception {
395         IpSecConfig ipSecConfig = new IpSecConfig();
396         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
397         addAuthAndCryptToIpSecConfig(ipSecConfig);
398 
399         IpSecTransformResponse createTransformResp =
400                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
401         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
402 
403         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
404     }
405 
406     @Test
testCreateTransformAead()407     public void testCreateTransformAead() throws Exception {
408         IpSecConfig ipSecConfig = new IpSecConfig();
409         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
410 
411         ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO);
412 
413         IpSecTransformResponse createTransformResp =
414                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
415         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
416 
417         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
418     }
419 
420     @Test
testCreateTransportModeTransformWithEncap()421     public void testCreateTransportModeTransformWithEncap() throws Exception {
422         IpSecUdpEncapResponse udpSock = mIpSecService.openUdpEncapsulationSocket(0, new Binder());
423 
424         IpSecConfig ipSecConfig = new IpSecConfig();
425         ipSecConfig.setMode(IpSecTransform.MODE_TRANSPORT);
426         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
427         addAuthAndCryptToIpSecConfig(ipSecConfig);
428         addEncapSocketToIpSecConfig(udpSock.resourceId, ipSecConfig);
429 
430         if (mFamily == AF_INET) {
431             IpSecTransformResponse createTransformResp =
432                     mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
433             assertEquals(IpSecManager.Status.OK, createTransformResp.status);
434 
435             verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
436         } else {
437             try {
438                 IpSecTransformResponse createTransformResp =
439                         mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
440                 fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
441             } catch (IllegalArgumentException expected) {
442             }
443         }
444     }
445 
446     @Test
testCreateTunnelModeTransformWithEncap()447     public void testCreateTunnelModeTransformWithEncap() throws Exception {
448         IpSecUdpEncapResponse udpSock = mIpSecService.openUdpEncapsulationSocket(0, new Binder());
449 
450         IpSecConfig ipSecConfig = new IpSecConfig();
451         ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
452         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
453         addAuthAndCryptToIpSecConfig(ipSecConfig);
454         addEncapSocketToIpSecConfig(udpSock.resourceId, ipSecConfig);
455 
456         if (mFamily == AF_INET) {
457             IpSecTransformResponse createTransformResp =
458                     mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
459             assertEquals(IpSecManager.Status.OK, createTransformResp.status);
460 
461             verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
462         } else {
463             try {
464                 IpSecTransformResponse createTransformResp =
465                         mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
466                 fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
467             } catch (IllegalArgumentException expected) {
468             }
469         }
470     }
471 
472     @Test
testCreateTwoTransformsWithSameSpis()473     public void testCreateTwoTransformsWithSameSpis() throws Exception {
474         IpSecConfig ipSecConfig = new IpSecConfig();
475         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
476         addAuthAndCryptToIpSecConfig(ipSecConfig);
477 
478         IpSecTransformResponse createTransformResp =
479                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
480         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
481 
482         // Attempting to create transform a second time with the same SPIs should throw an error...
483         try {
484             mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
485                 fail("IpSecService should have thrown an error for reuse of SPI");
486         } catch (IllegalStateException expected) {
487         }
488 
489         // ... even if the transform is deleted
490         mIpSecService.deleteTransform(createTransformResp.resourceId);
491         try {
492             mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
493                 fail("IpSecService should have thrown an error for reuse of SPI");
494         } catch (IllegalStateException expected) {
495         }
496     }
497 
498     @Test
testReleaseOwnedSpi()499     public void testReleaseOwnedSpi() throws Exception {
500         IpSecConfig ipSecConfig = new IpSecConfig();
501         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
502         addAuthAndCryptToIpSecConfig(ipSecConfig);
503 
504         IpSecTransformResponse createTransformResp =
505                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
506         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
507         assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
508         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
509         verify(mMockNetd, times(0))
510                 .ipSecDeleteSecurityAssociation(
511                         eq(mUid),
512                         anyString(),
513                         anyString(),
514                         eq(TEST_SPI),
515                         anyInt(),
516                         anyInt(),
517                         anyInt());
518         // quota is not released until the SPI is released by the Transform
519         assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
520     }
521 
522     @Test
testDeleteTransform()523     public void testDeleteTransform() throws Exception {
524         IpSecConfig ipSecConfig = new IpSecConfig();
525         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
526         addAuthAndCryptToIpSecConfig(ipSecConfig);
527 
528         IpSecTransformResponse createTransformResp =
529                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
530         mIpSecService.deleteTransform(createTransformResp.resourceId);
531 
532         verify(mMockNetd, times(1))
533                 .ipSecDeleteSecurityAssociation(
534                         eq(mUid),
535                         anyString(),
536                         anyString(),
537                         eq(TEST_SPI),
538                         anyInt(),
539                         anyInt(),
540                         anyInt());
541 
542         // Verify quota and RefcountedResource objects cleaned up
543         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
544         assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent);
545         assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
546 
547         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
548         // Verify that ipSecDeleteSa was not called when the SPI was released because the
549         // ownedByTransform property should prevent it; (note, the called count is cumulative).
550         verify(mMockNetd, times(1))
551                 .ipSecDeleteSecurityAssociation(
552                         anyInt(),
553                         anyString(),
554                         anyString(),
555                         anyInt(),
556                         anyInt(),
557                         anyInt(),
558                         anyInt());
559         assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
560 
561         try {
562             userRecord.mTransformRecords.getRefcountedResourceOrThrow(
563                     createTransformResp.resourceId);
564             fail("Expected IllegalArgumentException on attempt to access deleted resource");
565         } catch (IllegalArgumentException expected) {
566 
567         }
568     }
569 
570     @Test
testTransportModeTransformBinderDeath()571     public void testTransportModeTransformBinderDeath() throws Exception {
572         IpSecConfig ipSecConfig = new IpSecConfig();
573         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
574         addAuthAndCryptToIpSecConfig(ipSecConfig);
575 
576         IpSecTransformResponse createTransformResp =
577                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
578 
579         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
580         IpSecService.RefcountedResource refcountedRecord =
581                 userRecord.mTransformRecords.getRefcountedResourceOrThrow(
582                         createTransformResp.resourceId);
583 
584         refcountedRecord.binderDied();
585 
586         verify(mMockNetd)
587                 .ipSecDeleteSecurityAssociation(
588                         eq(mUid),
589                         anyString(),
590                         anyString(),
591                         eq(TEST_SPI),
592                         anyInt(),
593                         anyInt(),
594                         anyInt());
595 
596         // Verify quota and RefcountedResource objects cleaned up
597         assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent);
598         try {
599             userRecord.mTransformRecords.getRefcountedResourceOrThrow(
600                     createTransformResp.resourceId);
601             fail("Expected IllegalArgumentException on attempt to access deleted resource");
602         } catch (IllegalArgumentException expected) {
603 
604         }
605     }
606 
607     @Test
testApplyTransportModeTransform()608     public void testApplyTransportModeTransform() throws Exception {
609         verifyApplyTransportModeTransformCommon(false);
610     }
611 
612     @Test
testApplyTransportModeTransformReleasedSpi()613     public void testApplyTransportModeTransformReleasedSpi() throws Exception {
614         verifyApplyTransportModeTransformCommon(true);
615     }
616 
verifyApplyTransportModeTransformCommon( boolean closeSpiBeforeApply)617     public void verifyApplyTransportModeTransformCommon(
618                 boolean closeSpiBeforeApply) throws Exception {
619         IpSecConfig ipSecConfig = new IpSecConfig();
620         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
621         addAuthAndCryptToIpSecConfig(ipSecConfig);
622 
623         IpSecTransformResponse createTransformResp =
624                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
625 
626         if (closeSpiBeforeApply) {
627             mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
628         }
629 
630         Socket socket = new Socket();
631         socket.bind(null);
632         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
633 
634         int resourceId = createTransformResp.resourceId;
635         mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId);
636 
637         verify(mMockNetd)
638                 .ipSecApplyTransportModeTransform(
639                         eq(pfd),
640                         eq(mUid),
641                         eq(IpSecManager.DIRECTION_OUT),
642                         anyString(),
643                         anyString(),
644                         eq(TEST_SPI));
645     }
646 
647     @Test
testApplyTransportModeTransformWithClosedSpi()648     public void testApplyTransportModeTransformWithClosedSpi() throws Exception {
649         IpSecConfig ipSecConfig = new IpSecConfig();
650         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
651         addAuthAndCryptToIpSecConfig(ipSecConfig);
652 
653         IpSecTransformResponse createTransformResp =
654                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
655 
656         // Close SPI record
657         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
658 
659         Socket socket = new Socket();
660         socket.bind(null);
661         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
662 
663         int resourceId = createTransformResp.resourceId;
664         mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId);
665 
666         verify(mMockNetd)
667                 .ipSecApplyTransportModeTransform(
668                         eq(pfd),
669                         eq(mUid),
670                         eq(IpSecManager.DIRECTION_OUT),
671                         anyString(),
672                         anyString(),
673                         eq(TEST_SPI));
674     }
675 
676     @Test
testRemoveTransportModeTransform()677     public void testRemoveTransportModeTransform() throws Exception {
678         Socket socket = new Socket();
679         socket.bind(null);
680         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
681         mIpSecService.removeTransportModeTransforms(pfd);
682 
683         verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd);
684     }
685 
createAndValidateTunnel( String localAddr, String remoteAddr, String pkgName)686     private IpSecTunnelInterfaceResponse createAndValidateTunnel(
687             String localAddr, String remoteAddr, String pkgName) throws Exception {
688         final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel();
689         config.flags = new String[] {IF_STATE_DOWN};
690         when(mMockNetd.interfaceGetCfg(anyString())).thenReturn(config);
691         IpSecTunnelInterfaceResponse createTunnelResp =
692                 mIpSecService.createTunnelInterface(
693                         mSourceAddr, mDestinationAddr, fakeNetwork, new Binder(), pkgName);
694 
695         assertNotNull(createTunnelResp);
696         assertEquals(IpSecManager.Status.OK, createTunnelResp.status);
697         for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT, DIRECTION_FWD}) {
698             for (int selAddrFamily : ADDRESS_FAMILIES) {
699                 verify(mMockNetd).ipSecAddSecurityPolicy(
700                         eq(mUid),
701                         eq(selAddrFamily),
702                         eq(direction),
703                         anyString(),
704                         anyString(),
705                         eq(0),
706                         anyInt(), // iKey/oKey
707                         anyInt(), // mask
708                         eq(createTunnelResp.resourceId));
709             }
710         }
711 
712         return createTunnelResp;
713     }
714 
715     @Test
testCreateTunnelInterface()716     public void testCreateTunnelInterface() throws Exception {
717         IpSecTunnelInterfaceResponse createTunnelResp =
718                 createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
719 
720         // Check that we have stored the tracking object, and retrieve it
721         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
722         IpSecService.RefcountedResource refcountedRecord =
723                 userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
724                         createTunnelResp.resourceId);
725 
726         assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent);
727         verify(mMockNetd)
728                 .ipSecAddTunnelInterface(
729                         eq(createTunnelResp.interfaceName),
730                         eq(mSourceAddr),
731                         eq(mDestinationAddr),
732                         anyInt(),
733                         anyInt(),
734                         anyInt());
735         verify(mMockNetd).interfaceSetCfg(argThat(
736                 config -> Arrays.asList(config.flags).contains(IF_STATE_UP)));
737     }
738 
739     @Test
testDeleteTunnelInterface()740     public void testDeleteTunnelInterface() throws Exception {
741         IpSecTunnelInterfaceResponse createTunnelResp =
742                 createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
743 
744         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
745 
746         mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE);
747 
748         // Verify quota and RefcountedResource objects cleaned up
749         assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
750         verify(mMockNetd).ipSecRemoveTunnelInterface(eq(createTunnelResp.interfaceName));
751         try {
752             userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
753                     createTunnelResp.resourceId);
754             fail("Expected IllegalArgumentException on attempt to access deleted resource");
755         } catch (IllegalArgumentException expected) {
756         }
757     }
758 
createFakeUnderlyingNetwork(String interfaceName)759     private Network createFakeUnderlyingNetwork(String interfaceName) {
760         final Network fakeNetwork = new Network(1000);
761         final LinkProperties fakeLp = new LinkProperties();
762         fakeLp.setInterfaceName(interfaceName);
763         when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp);
764         return fakeNetwork;
765     }
766 
767     @Test
testSetNetworkForTunnelInterface()768     public void testSetNetworkForTunnelInterface() throws Exception {
769         final IpSecTunnelInterfaceResponse createTunnelResp =
770                 createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
771         final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface");
772         final int tunnelIfaceResourceId = createTunnelResp.resourceId;
773         mIpSecService.setNetworkForTunnelInterface(
774                 tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
775 
776         final IpSecService.UserRecord userRecord =
777                 mIpSecService.mUserResourceTracker.getUserRecord(mUid);
778         assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent);
779 
780         final TunnelInterfaceRecord tunnelInterfaceInfo =
781                 userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
782         assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork());
783     }
784 
785     @Test
testSetNetworkForTunnelInterfaceFailsForInvalidResourceId()786     public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
787         final IpSecTunnelInterfaceResponse createTunnelResp =
788                 createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
789         final Network newFakeNetwork = new Network(1000);
790 
791         try {
792             mIpSecService.setNetworkForTunnelInterface(
793                     IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE);
794             fail("Expected an IllegalArgumentException for invalid resource ID.");
795         } catch (IllegalArgumentException expected) {
796         }
797     }
798 
799     @Test
testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork()800     public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception {
801         final IpSecTunnelInterfaceResponse createTunnelResp =
802                 createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
803         final int tunnelIfaceResourceId = createTunnelResp.resourceId;
804         final IpSecService.UserRecord userRecord =
805                 mIpSecService.mUserResourceTracker.getUserRecord(mUid);
806         final TunnelInterfaceRecord tunnelInterfaceInfo =
807                 userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
808 
809         final Network newFakeNetwork =
810                 createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName());
811 
812         try {
813             mIpSecService.setNetworkForTunnelInterface(
814                     tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
815             fail(
816                     "Expected an IllegalArgumentException because the underlying network is the"
817                             + " network being exposed by this tunnel.");
818         } catch (IllegalArgumentException expected) {
819         }
820     }
821 
822     @Test
testTunnelInterfaceBinderDeath()823     public void testTunnelInterfaceBinderDeath() throws Exception {
824         IpSecTunnelInterfaceResponse createTunnelResp =
825                 createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
826 
827         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
828         IpSecService.RefcountedResource refcountedRecord =
829                 userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
830                         createTunnelResp.resourceId);
831 
832         refcountedRecord.binderDied();
833 
834         // Verify quota and RefcountedResource objects cleaned up
835         assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
836         verify(mMockNetd).ipSecRemoveTunnelInterface(eq(createTunnelResp.interfaceName));
837         try {
838             userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
839                     createTunnelResp.resourceId);
840             fail("Expected IllegalArgumentException on attempt to access deleted resource");
841         } catch (IllegalArgumentException expected) {
842         }
843     }
844 
845     @Test
testApplyTunnelModeTransformOutbound()846     public void testApplyTunnelModeTransformOutbound() throws Exception {
847         verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT);
848     }
849 
850     @Test
testApplyTunnelModeTransformOutboundNonNetworkStack()851     public void testApplyTunnelModeTransformOutboundNonNetworkStack() throws Exception {
852         mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS);
853         verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT);
854     }
855 
856     @Test
testApplyTunnelModeTransformOutboundReleasedSpi()857     public void testApplyTunnelModeTransformOutboundReleasedSpi() throws Exception {
858         verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_OUT);
859     }
860 
861     @Test
testApplyTunnelModeTransformInbound()862     public void testApplyTunnelModeTransformInbound() throws Exception {
863         verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN);
864     }
865 
866     @Test
testApplyTunnelModeTransformInboundNonNetworkStack()867     public void testApplyTunnelModeTransformInboundNonNetworkStack() throws Exception {
868         mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS);
869         verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN);
870     }
871 
872     @Test
testApplyTunnelModeTransformForward()873     public void testApplyTunnelModeTransformForward() throws Exception {
874         verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD);
875     }
876 
877     @Test
testApplyTunnelModeTransformForwardNonNetworkStack()878     public void testApplyTunnelModeTransformForwardNonNetworkStack() throws Exception {
879         mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS);
880 
881         try {
882             verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD);
883             fail("Expected security exception due to use of forward policies without NETWORK_STACK"
884                      + " or MAINLINE_NETWORK_STACK permission");
885         } catch (SecurityException expected) {
886         }
887     }
888 
verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply, int direction)889     public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply, int direction)
890             throws Exception {
891         IpSecConfig ipSecConfig = new IpSecConfig();
892         ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
893         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
894         addAuthAndCryptToIpSecConfig(ipSecConfig);
895 
896         IpSecTransformResponse createTransformResp =
897                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
898         IpSecTunnelInterfaceResponse createTunnelResp =
899                 createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
900 
901         if (closeSpiBeforeApply) {
902             mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
903         }
904 
905         int transformResourceId = createTransformResp.resourceId;
906         int tunnelResourceId = createTunnelResp.resourceId;
907         mIpSecService.applyTunnelModeTransform(
908                 tunnelResourceId, direction, transformResourceId, BLESSED_PACKAGE);
909 
910         for (int selAddrFamily : ADDRESS_FAMILIES) {
911             verify(mMockNetd)
912                     .ipSecUpdateSecurityPolicy(
913                             eq(mUid),
914                             eq(selAddrFamily),
915                             eq(direction),
916                             anyString(),
917                             anyString(),
918                             eq(direction == DIRECTION_OUT ? TEST_SPI : 0),
919                             anyInt(), // iKey/oKey
920                             anyInt(), // mask
921                             eq(tunnelResourceId));
922         }
923 
924         ipSecConfig.setXfrmInterfaceId(tunnelResourceId);
925         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
926     }
927 
928 
929     @Test
testApplyTunnelModeTransformWithClosedSpi()930     public void testApplyTunnelModeTransformWithClosedSpi() throws Exception {
931         IpSecConfig ipSecConfig = new IpSecConfig();
932         ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
933         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
934         addAuthAndCryptToIpSecConfig(ipSecConfig);
935 
936         IpSecTransformResponse createTransformResp =
937                 mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
938         IpSecTunnelInterfaceResponse createTunnelResp =
939                 createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
940 
941         // Close SPI record
942         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
943 
944         int transformResourceId = createTransformResp.resourceId;
945         int tunnelResourceId = createTunnelResp.resourceId;
946         mIpSecService.applyTunnelModeTransform(
947                 tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
948 
949         for (int selAddrFamily : ADDRESS_FAMILIES) {
950             verify(mMockNetd)
951                     .ipSecUpdateSecurityPolicy(
952                             eq(mUid),
953                             eq(selAddrFamily),
954                             eq(IpSecManager.DIRECTION_OUT),
955                             anyString(),
956                             anyString(),
957                             eq(TEST_SPI),
958                             anyInt(), // iKey/oKey
959                             anyInt(), // mask
960                             eq(tunnelResourceId));
961         }
962 
963         ipSecConfig.setXfrmInterfaceId(tunnelResourceId);
964         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
965     }
966 
967     @Test
testAddRemoveAddressFromTunnelInterface()968     public void testAddRemoveAddressFromTunnelInterface() throws Exception {
969         for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) {
970             IpSecTunnelInterfaceResponse createTunnelResp =
971                     createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName);
972             mIpSecService.addAddressToTunnelInterface(
973                     createTunnelResp.resourceId, mLocalInnerAddress, pkgName);
974             verify(mMockNetd, times(1))
975                     .interfaceAddAddress(
976                             eq(createTunnelResp.interfaceName),
977                             eq(mLocalInnerAddress.getAddress().getHostAddress()),
978                             eq(mLocalInnerAddress.getPrefixLength()));
979             mIpSecService.removeAddressFromTunnelInterface(
980                     createTunnelResp.resourceId, mLocalInnerAddress, pkgName);
981             verify(mMockNetd, times(1))
982                     .interfaceDelAddress(
983                             eq(createTunnelResp.interfaceName),
984                             eq(mLocalInnerAddress.getAddress().getHostAddress()),
985                             eq(mLocalInnerAddress.getPrefixLength()));
986             mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, pkgName);
987         }
988     }
989 
990     @Ignore
991     @Test
testAddTunnelFailsForBadPackageName()992     public void testAddTunnelFailsForBadPackageName() throws Exception {
993         try {
994             IpSecTunnelInterfaceResponse createTunnelResp =
995                     createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE);
996             fail("Expected a SecurityException for badPackage.");
997         } catch (SecurityException expected) {
998         }
999     }
1000 
1001     @Test
testFeatureFlagVerification()1002     public void testFeatureFlagVerification() throws Exception {
1003         when(mMockPkgMgr.hasSystemFeature(eq(PackageManager.FEATURE_IPSEC_TUNNELS)))
1004                 .thenReturn(false);
1005 
1006         try {
1007             String addr = Inet4Address.getLoopbackAddress().getHostAddress();
1008             mIpSecService.createTunnelInterface(
1009                     addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE);
1010             fail("Expected UnsupportedOperationException for disabled feature");
1011         } catch (UnsupportedOperationException expected) {
1012         }
1013     }
1014 }
1015