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 = 180000; 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 break; 126 default: 127 super.handleMessage(msg); 128 } 129 } 130 } 131 132 class IsolatedConnection implements ServiceConnection { 133 private final ConditionVariable mConnectedCondition = new ConditionVariable(); 134 private Messenger mService; 135 onServiceConnected(ComponentName name, IBinder binder)136 public void onServiceConnected(ComponentName name, IBinder binder) { 137 mService = new Messenger(binder); 138 mConnectedCondition.open(); 139 } 140 onServiceDisconnected(ComponentName name)141 public void onServiceDisconnected(ComponentName name) { 142 mConnectedCondition.close(); 143 } 144 getTestResult()145 public boolean getTestResult() { 146 boolean connected = mConnectedCondition.block(SERVICE_START_TIMEOUT_MS); 147 if (!connected) { 148 Log.e(TAG, "Failed to wait for IsolatedService to bind."); 149 return false; 150 } 151 Message msg = Message.obtain(null, IsolatedService.MSG_GET_SECCOMP_RESULT); 152 msg.replyTo = mMessenger; 153 try { 154 mService.send(msg); 155 } catch (RemoteException e) { 156 Log.e(TAG, "Failed to send message to IsolatedService to get test result.", e); 157 return false; 158 } 159 160 // Wait for result and return it 161 mResultCondition.block(); 162 163 return mAppZygoteResult; 164 } 165 } 166 bindService(Class<?> cls)167 private IsolatedConnection bindService(Class<?> cls) { 168 Intent intent = new Intent(); 169 intent.setClass(mContext, cls); 170 IsolatedConnection conn = new IsolatedConnection(); 171 mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); 172 173 return conn; 174 } 175 176 @Test testAppZygoteSyscalls()177 public void testAppZygoteSyscalls() { 178 // Isolated services that spawn from the application Zygote are allowed 179 // to preload code in a security context that is allowed to use 180 // setresuid() / setresgid() in a limited range; this test enforces 181 // we allow calls within the range, and reject those outside them. 182 // This is done from the ZygotePreload class (which runs in the app zygote 183 // context); here we just wait for the service to come up, and ask it 184 // whether the tests were executed successfully. We have to ask the service 185 // because that is the only process that we can talk to that shares the 186 // same address space as the ZygotePreload class, which holds the test 187 // result. 188 IsolatedConnection conn = bindService(IsolatedService.class); 189 boolean testResult = conn.getTestResult(); 190 Assert.assertTrue("seccomp tests in application zygote failed; see logs.", testResult); 191 } 192 getCurrentArch()193 private static String getCurrentArch() { 194 if (CpuFeatures.isArm64Cpu()) { 195 return "arm64"; 196 } else if (CpuFeatures.isArmCpu()) { 197 return "arm"; 198 } else if (CpuFeatures.isX86_64Cpu()) { 199 return "x86_64"; 200 } else if (CpuFeatures.isX86Cpu()) { 201 return "x86"; 202 } else if (CpuFeatures.isRiscv64Cpu()) { 203 return "riscv64"; 204 }else { 205 Assert.fail("Unsupported architecture"); 206 return null; 207 } 208 } 209 readInputStreamFully(InputStream is)210 private String readInputStreamFully(InputStream is) throws IOException { 211 StringBuilder sb = new StringBuilder(); 212 byte[] buffer = new byte[4096]; 213 while (is.available() > 0) { 214 int size = is.read(buffer); 215 if (size < 0) { 216 break; 217 } 218 sb.append(new String(buffer, 0, size)); 219 } 220 return sb.toString(); 221 } 222 testBlocked(int nr)223 private void testBlocked(int nr) { 224 Assert.assertTrue("Syscall " + nr + " not blocked", testSyscallBlocked(nr)); 225 } 226 testAllowed(int nr)227 private void testAllowed(int nr) { 228 Assert.assertFalse("Syscall " + nr + " blocked", testSyscallBlocked(nr)); 229 } 230 testSyscallBlocked(int nr)231 private static final native boolean testSyscallBlocked(int nr); testSetresuidBlocked(int ruid, int euid, int suid)232 protected static final native boolean testSetresuidBlocked(int ruid, int euid, int suid); testSetresgidBlocked(int rgid, int egid, int sgid)233 protected static final native boolean testSetresgidBlocked(int rgid, int egid, int sgid); 234 } 235