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 android.seccomp.cts.app; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.ServiceConnection; 23 import android.content.res.AssetManager; 24 import android.os.ConditionVariable; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.os.IBinder; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.os.Messenger; 31 import android.os.RemoteException; 32 import android.util.Log; 33 34 import androidx.test.InstrumentationRegistry; 35 import androidx.test.runner.AndroidJUnit4; 36 37 import com.android.compatibility.common.util.CpuFeatures; 38 39 import org.json.JSONException; 40 import org.json.JSONObject; 41 import org.junit.Assert; 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.util.Iterator; 49 50 /** 51 * Device-side tests for CtsSeccompHostTestCases 52 */ 53 @RunWith(AndroidJUnit4.class) 54 public class SeccompDeviceTest { 55 static { 56 System.loadLibrary("ctsseccomp_jni"); 57 } 58 59 static final String TAG = "SeccompDeviceTest"; 60 61 protected Context mContext; 62 63 // This is flagged whenever we have obtained the test result from the isolated 64 // service; it allows us to block the test until the results are in. 65 private final ConditionVariable mResultCondition = new ConditionVariable(); 66 67 private boolean mAppZygoteResult; 68 private Messenger mMessenger; 69 private HandlerThread mHandlerThread; 70 71 // The service start can take a long time, because seccomp denials will 72 // cause process crashes and dumps, which we waitpid() for sequentially. 73 private static final int SERVICE_START_TIMEOUT_MS = 120000; 74 75 private JSONObject mAllowedSyscallMap; 76 private JSONObject mBlockedSyscallMap; 77 78 @Before initializeSyscallMap()79 public void initializeSyscallMap() throws IOException, JSONException { 80 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 81 AssetManager manager = mContext.getAssets(); 82 try (InputStream is = manager.open("syscalls_allowed.json")) { 83 mAllowedSyscallMap = new JSONObject(readInputStreamFully(is)); 84 } 85 try (InputStream is = manager.open("syscalls_blocked.json")) { 86 mBlockedSyscallMap = new JSONObject(readInputStreamFully(is)); 87 } 88 mHandlerThread = new HandlerThread("HandlerThread"); 89 mHandlerThread.start(); 90 Looper looper = mHandlerThread.getLooper(); 91 mMessenger = new Messenger(new IncomingHandler(looper)); 92 } 93 94 @Test testCTSSyscallAllowed()95 public void testCTSSyscallAllowed() throws JSONException { 96 JSONObject map = mAllowedSyscallMap.getJSONObject(getCurrentArch()); 97 Iterator<String> iter = map.keys(); 98 while (iter.hasNext()) { 99 String syscallName = iter.next(); 100 testAllowed(map.getInt(syscallName)); 101 } 102 } 103 104 @Test testCTSSyscallBlocked()105 public void testCTSSyscallBlocked() throws JSONException { 106 JSONObject map = mBlockedSyscallMap.getJSONObject(getCurrentArch()); 107 Iterator<String> iter = map.keys(); 108 while (iter.hasNext()) { 109 String syscallName = iter.next(); 110 testBlocked(map.getInt(syscallName)); 111 } 112 } 113 114 class IncomingHandler extends Handler { IncomingHandler(Looper looper)115 IncomingHandler(Looper looper) { 116 super(looper); 117 } 118 119 @Override handleMessage(Message msg)120 public void handleMessage(Message msg) { 121 switch (msg.what) { 122 case IsolatedService.MSG_SECCOMP_RESULT: 123 mAppZygoteResult = (msg.arg1 == 1); 124 mResultCondition.open(); 125 default: 126 super.handleMessage(msg); 127 } 128 } 129 } 130 131 class IsolatedConnection implements ServiceConnection { 132 private final ConditionVariable mConnectedCondition = new ConditionVariable(); 133 private Messenger mService; 134 onServiceConnected(ComponentName name, IBinder binder)135 public void onServiceConnected(ComponentName name, IBinder binder) { 136 mService = new Messenger(binder); 137 mConnectedCondition.open(); 138 } 139 onServiceDisconnected(ComponentName name)140 public void onServiceDisconnected(ComponentName name) { 141 mConnectedCondition.close(); 142 } 143 getTestResult()144 public boolean getTestResult() { 145 boolean connected = mConnectedCondition.block(SERVICE_START_TIMEOUT_MS); 146 if (!connected) { 147 Log.e(TAG, "Failed to wait for IsolatedService to bind."); 148 return false; 149 } 150 Message msg = Message.obtain(null, IsolatedService.MSG_GET_SECCOMP_RESULT); 151 msg.replyTo = mMessenger; 152 try { 153 mService.send(msg); 154 } catch (RemoteException e) { 155 Log.e(TAG, "Failed to send message to IsolatedService to get test result.", e); 156 return false; 157 } 158 159 // Wait for result and return it 160 mResultCondition.block(); 161 162 return mAppZygoteResult; 163 } 164 } 165 bindService(Class<?> cls)166 private IsolatedConnection bindService(Class<?> cls) { 167 Intent intent = new Intent(); 168 intent.setClass(mContext, cls); 169 IsolatedConnection conn = new IsolatedConnection(); 170 mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); 171 172 return conn; 173 } 174 175 @Test testAppZygoteSyscalls()176 public void testAppZygoteSyscalls() { 177 // Isolated services that spawn from the application Zygote are allowed 178 // to preload code in a security context that is allowed to use 179 // setresuid() / setresgid() in a limited range; this test enforces 180 // we allow calls within the range, and reject those outside them. 181 // This is done from the ZygotePreload class (which runs in the app zygote 182 // context); here we just wait for the service to come up, and ask it 183 // whether the tests were executed successfully. We have to ask the service 184 // because that is the only process that we can talk to that shares the 185 // same address space as the ZygotePreload class, which holds the test 186 // result. 187 IsolatedConnection conn = bindService(IsolatedService.class); 188 boolean testResult = conn.getTestResult(); 189 Assert.assertTrue("seccomp tests in application zygote failed; see logs.", testResult); 190 } 191 getCurrentArch()192 private static String getCurrentArch() { 193 if (CpuFeatures.isArm64Cpu()) { 194 return "arm64"; 195 } else if (CpuFeatures.isArmCpu()) { 196 return "arm"; 197 } else if (CpuFeatures.isX86_64Cpu()) { 198 return "x86_64"; 199 } else if (CpuFeatures.isX86Cpu()) { 200 return "x86"; 201 } else { 202 Assert.fail("Unsupported architecture"); 203 return null; 204 } 205 } 206 readInputStreamFully(InputStream is)207 private String readInputStreamFully(InputStream is) throws IOException { 208 StringBuilder sb = new StringBuilder(); 209 byte[] buffer = new byte[4096]; 210 while (is.available() > 0) { 211 int size = is.read(buffer); 212 if (size < 0) { 213 break; 214 } 215 sb.append(new String(buffer, 0, size)); 216 } 217 return sb.toString(); 218 } 219 testBlocked(int nr)220 private void testBlocked(int nr) { 221 Assert.assertTrue("Syscall " + nr + " not blocked", testSyscallBlocked(nr)); 222 } 223 testAllowed(int nr)224 private void testAllowed(int nr) { 225 Assert.assertFalse("Syscall " + nr + " blocked", testSyscallBlocked(nr)); 226 } 227 testSyscallBlocked(int nr)228 private static final native boolean testSyscallBlocked(int nr); testSetresuidBlocked(int ruid, int euid, int suid)229 protected static final native boolean testSetresuidBlocked(int ruid, int euid, int suid); testSetresgidBlocked(int rgid, int egid, int sgid)230 protected static final native boolean testSetresgidBlocked(int rgid, int egid, int sgid); 231 } 232