1 /* 2 * Copyright (C) 2014 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.os.cts; 18 19 import android.app.Service; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.content.res.AssetFileDescriptor; 25 import android.content.res.AssetManager; 26 import android.os.Environment; 27 import android.os.IBinder; 28 import android.os.ParcelFileDescriptor; 29 import android.os.Process; 30 import android.os.RemoteException; 31 import android.os.MemoryFile; 32 import android.os.SystemClock; 33 import android.os.Build; 34 import android.util.Log; 35 import android.test.AndroidTestCase; 36 37 import com.android.compatibility.common.util.CpuFeatures; 38 import com.google.common.util.concurrent.AbstractFuture; 39 40 import java.io.File; 41 import java.io.FileInputStream; 42 import java.io.FileOutputStream; 43 import java.io.FileNotFoundException; 44 import java.io.IOException; 45 import java.util.concurrent.ExecutionException; 46 import java.util.concurrent.TimeUnit; 47 import java.util.concurrent.TimeoutException; 48 import java.util.Date; 49 50 public class SeccompTest extends AndroidTestCase { 51 final static String TAG = "SeccompTest"; 52 53 static { 54 System.loadLibrary("ctsos_jni"); 55 } 56 57 // As this test validates a kernel system call interface, if the CTS tests 58 // were built for ARM but are running on an x86 CPU, the system call numbers 59 // will not be correct, so skip those tests. isRunningUnderEmulatedAbi()60 private boolean isRunningUnderEmulatedAbi() { 61 final String primaryAbi = Build.SUPPORTED_ABIS[0]; 62 return (CpuFeatures.isArmCpu() || CpuFeatures.isArm64Cpu()) && 63 !(primaryAbi.equals("armeabi-v7a") || primaryAbi.equals("arm64-v8a")); 64 } 65 testSeccomp()66 public void testSeccomp() { 67 if (OSFeatures.needsSeccompSupport()) { 68 assertTrue("Please enable seccomp support " 69 + "in your kernel (CONFIG_SECCOMP_FILTER=y)", 70 OSFeatures.hasSeccompSupport()); 71 } 72 } 73 testKernelBasicTests()74 public void testKernelBasicTests() { 75 if (!OSFeatures.needsSeccompSupport()) 76 return; 77 78 if (isRunningUnderEmulatedAbi()) { 79 Log.d(TAG, "Skipping test running under an emulated ABI"); 80 return; 81 } 82 83 final String[] tests = { 84 /* "global.mode_strict_support", // all Android processes already have seccomp filter */ 85 /* "global.mode_strict_cannot_call_prctl", // all Android processes already have seccomp 86 * filter */ 87 "global.no_new_privs_support", 88 "global.mode_filter_support", 89 /* "global.mode_filter_without_nnp", // all Android processes already have nnp */ 90 "global.filter_size_limits", 91 "global.filter_chain_limits", 92 "global.mode_filter_cannot_move_to_strict", 93 /* "global.mode_filter_get_seccomp", // all Android processes already have seccomp 94 * filter */ 95 "global.ALLOW_all", 96 "global.empty_prog", 97 "global.unknown_ret_is_kill_inside", 98 "global.unknown_ret_is_kill_above_allow", 99 "global.KILL_all", 100 "global.KILL_one", 101 "global.KILL_one_arg_one", 102 "global.KILL_one_arg_six", 103 "global.arg_out_of_range", 104 "global.ERRNO_valid", 105 "global.ERRNO_zero", 106 /* "global.ERRNO_capped", // presently fails */ 107 }; 108 runKernelUnitTestSuite(tests); 109 } 110 testKernelTrapTests()111 public void testKernelTrapTests() { 112 if (!OSFeatures.needsSeccompSupport()) 113 return; 114 115 final String[] tests = { 116 "TRAP.dfl", 117 "TRAP.ign", 118 "TRAP.handler", 119 }; 120 runKernelUnitTestSuite(tests); 121 } 122 testKernelPrecedenceTests()123 public void testKernelPrecedenceTests() { 124 if (!OSFeatures.needsSeccompSupport()) 125 return; 126 127 final String[] tests = { 128 "precedence.allow_ok", 129 "precedence.kill_is_highest", 130 "precedence.kill_is_highest_in_any_order", 131 "precedence.trap_is_second", 132 "precedence.trap_is_second_in_any_order", 133 "precedence.errno_is_third", 134 "precedence.errno_is_third_in_any_order", 135 "precedence.trace_is_fourth", 136 "precedence.trace_is_fourth_in_any_order", 137 }; 138 runKernelUnitTestSuite(tests); 139 } 140 141 /* // The SECCOMP_RET_TRACE does not work under Android Arm32. 142 public void testKernelTraceTests() { 143 if (!OSFeatures.needsSeccompSupport()) 144 return; 145 146 final String[] tests = { 147 "TRACE_poke.read_has_side_effects", 148 "TRACE_poke.getpid_runs_normally", 149 "TRACE_syscall.syscall_allowed", 150 "TRACE_syscall.syscall_redirected", 151 "TRACE_syscall.syscall_dropped", 152 }; 153 runKernelUnitTestSuite(tests); 154 } 155 */ 156 testKernelTSYNCTests()157 public void testKernelTSYNCTests() { 158 if (!OSFeatures.needsSeccompSupport()) 159 return; 160 161 if (isRunningUnderEmulatedAbi()) { 162 Log.d(TAG, "Skipping test running under an emulated ABI"); 163 return; 164 } 165 166 final String[] tests = { 167 "global.seccomp_syscall", 168 "global.seccomp_syscall_mode_lock", 169 "global.TSYNC_first", 170 "TSYNC.siblings_fail_prctl", 171 "TSYNC.two_siblings_with_ancestor", 172 /* "TSYNC.two_sibling_want_nnp", // all Android processes already have nnp */ 173 "TSYNC.two_siblings_with_no_filter", 174 "TSYNC.two_siblings_with_one_divergence", 175 "TSYNC.two_siblings_not_under_filter", 176 /* "global.syscall_restart", // ptrace attach fails */ 177 }; 178 runKernelUnitTestSuite(tests); 179 } 180 181 /** 182 * Runs a kernel unit test suite (an array of kernel test names). 183 */ runKernelUnitTestSuite(final String[] tests)184 private void runKernelUnitTestSuite(final String[] tests) { 185 for (final String test : tests) { 186 // TODO: Replace the URL with the documentation when it's finished. 187 assertTrue(test + " failed. This test requires kernel functionality to pass. Please go to " 188 + "http://source.android.com/devices/tech/config/kernel.html#Seccomp-BPF-TSYNC" 189 + " for instructions on how to enable or backport the required functionality.", 190 runKernelUnitTest(test)); 191 } 192 } 193 194 /** 195 * Integration test for seccomp-bpf policy applied to an isolatedProcess=true 196 * service. This will perform various operations in an isolated process under a 197 * fairly restrictive seccomp policy. 198 */ testIsolatedServicePolicy()199 public void testIsolatedServicePolicy() throws InterruptedException, ExecutionException, 200 RemoteException { 201 if (!OSFeatures.needsSeccompSupport()) 202 return; 203 204 if (isRunningUnderEmulatedAbi()) { 205 Log.d(TAG, "Skipping test running under an emulated ABI"); 206 return; 207 } 208 209 final IsolatedServiceConnection peer = new IsolatedServiceConnection(); 210 final Intent intent = new Intent(getContext(), IsolatedService.class); 211 assertTrue(getContext().bindService(intent, peer, Context.BIND_AUTO_CREATE)); 212 213 final ISeccompIsolatedService service = peer.get(); 214 215 // installFilter() must be called first, to set the seccomp policy. 216 assertTrue(service.installFilter()); 217 assertTrue(service.createThread()); 218 assertTrue(service.getSystemInfo()); 219 doFileWriteTest(service); 220 assertTrue(service.openAshmem()); 221 assertTrue(service.openDevFile()); 222 223 getContext().unbindService(peer); 224 } 225 226 /** 227 * Integration test for seccomp-bpf policy with isolatedProcess, where the 228 * process then violates the policy and gets killed by the kernel. 229 */ testViolateIsolatedServicePolicy()230 public void testViolateIsolatedServicePolicy() throws InterruptedException, 231 ExecutionException, RemoteException { 232 if (!OSFeatures.needsSeccompSupport()) 233 return; 234 235 if (isRunningUnderEmulatedAbi()) { 236 Log.d(TAG, "Skipping test running under an emulated ABI"); 237 return; 238 } 239 240 final IsolatedServiceConnection peer = new IsolatedServiceConnection(); 241 final Intent intent = new Intent(getContext(), IsolatedService.class); 242 assertTrue(getContext().bindService(intent, peer, Context.BIND_AUTO_CREATE)); 243 244 final ISeccompIsolatedService service = peer.get(); 245 246 assertTrue(service.installFilter()); 247 boolean gotRemoteException = false; 248 try { 249 service.violatePolicy(); 250 } catch (RemoteException e) { 251 gotRemoteException = true; 252 } 253 assertTrue(gotRemoteException); 254 255 getContext().unbindService(peer); 256 } 257 doFileWriteTest(ISeccompIsolatedService service)258 private void doFileWriteTest(ISeccompIsolatedService service) throws RemoteException { 259 final String fileName = "seccomp_test"; 260 ParcelFileDescriptor fd = null; 261 try { 262 FileOutputStream fOut = getContext().openFileOutput(fileName, 0); 263 fd = ParcelFileDescriptor.dup(fOut.getFD()); 264 fOut.close(); 265 } catch (FileNotFoundException e) { 266 fail(e.getMessage()); 267 return; 268 } catch (IOException e) { 269 fail(e.getMessage()); 270 return; 271 } 272 273 assertTrue(service.writeToFile(fd)); 274 275 try { 276 FileInputStream fIn = getContext().openFileInput(fileName); 277 assertEquals('!', fIn.read()); 278 fIn.close(); 279 } catch (FileNotFoundException e) { 280 fail(e.getMessage()); 281 } catch (IOException e) { 282 fail(e.getMessage()); 283 } 284 } 285 286 class IsolatedServiceConnection extends AbstractFuture<ISeccompIsolatedService> 287 implements ServiceConnection { 288 @Override onServiceConnected(ComponentName name, IBinder service)289 public void onServiceConnected(ComponentName name, IBinder service) { 290 set(ISeccompIsolatedService.Stub.asInterface(service)); 291 } 292 293 @Override onServiceDisconnected(ComponentName name)294 public void onServiceDisconnected(ComponentName name) { 295 } 296 297 @Override get()298 public ISeccompIsolatedService get() throws InterruptedException, ExecutionException { 299 try { 300 return get(10, TimeUnit.SECONDS); 301 } catch (TimeoutException e) { 302 throw new RuntimeException(e); 303 } 304 } 305 } 306 307 public static class IsolatedService extends Service { 308 private final ISeccompIsolatedService.Stub mService = new ISeccompIsolatedService.Stub() { 309 public boolean installFilter() { 310 return installTestFilter(getAssets()); 311 } 312 313 public boolean createThread() { 314 Thread thread = new Thread(new Runnable() { 315 @Override 316 public void run() { 317 try { 318 Thread.currentThread().setPriority(Thread.MIN_PRIORITY); 319 Thread.sleep(100); 320 } catch (InterruptedException e) { 321 } 322 } 323 }); 324 thread.run(); 325 try { 326 thread.join(); 327 } catch (InterruptedException e) { 328 return false; 329 } 330 return true; 331 } 332 333 public boolean getSystemInfo() { 334 long uptimeMillis = SystemClock.uptimeMillis(); 335 if (uptimeMillis < 1) { 336 Log.d(TAG, "SystemClock failed"); 337 return false; 338 } 339 340 String version = Build.VERSION.CODENAME; 341 if (version.length() == 0) { 342 Log.d(TAG, "Build.VERSION failed"); 343 return false; 344 } 345 346 long time = (new Date()).getTime(); 347 if (time < 100) { 348 Log.d(TAG, "getTime failed"); 349 return false; 350 } 351 352 return true; 353 } 354 355 public boolean writeToFile(ParcelFileDescriptor fd) { 356 FileOutputStream fOut = new FileOutputStream(fd.getFileDescriptor()); 357 try { 358 fOut.write('!'); 359 fOut.close(); 360 } catch (IOException e) { 361 return false; 362 } 363 return true; 364 } 365 366 public boolean openAshmem() { 367 byte[] buffer = {'h', 'e', 'l', 'l', 'o'}; 368 try { 369 MemoryFile file = new MemoryFile("seccomp_isolated_test", 32); 370 file.writeBytes(buffer, 0, 0, buffer.length); 371 file.close(); 372 return true; 373 } catch (IOException e) { 374 return false; 375 } 376 } 377 378 public boolean openDevFile() { 379 try { 380 FileInputStream fIn = new FileInputStream("/dev/zero"); 381 boolean succeed = fIn.read() == 0; 382 succeed &= fIn.read() == 0; 383 succeed &= fIn.read() == 0; 384 fIn.close(); 385 return succeed; 386 } catch (FileNotFoundException e) { 387 return false; 388 } catch (IOException e) { 389 return false; 390 } 391 } 392 393 public void violatePolicy() { 394 getClockBootTime(); 395 } 396 }; 397 398 @Override onBind(Intent intent)399 public IBinder onBind(Intent intent) { 400 return mService; 401 } 402 } 403 404 /** 405 * Loads an architecture-specific policy file from the AssetManager and 406 * installs it using Minijail. 407 */ installTestFilter(final AssetManager assets)408 private static boolean installTestFilter(final AssetManager assets) { 409 final String arch = getPolicyAbiString(); 410 if (arch == null) { 411 throw new RuntimeException("Unsupported architecture/ABI"); 412 } 413 414 try { 415 // Create a pipe onto which we will write the composite Seccomp policy. 416 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 417 final ParcelFileDescriptor.AutoCloseOutputStream outputStream = 418 new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]); 419 420 // The policy files to concat together. 421 final AssetFileDescriptor[] policyFiles = { 422 assets.openFd("minijail/isolated-" + arch + ".policy"), 423 assets.openFd("minijail/isolated-common.policy"), 424 arch.equals("i386") ? null : assets.openFd("minijail/isolated-common-not-i386.policy"), 425 }; 426 427 // Convert our PID to ASCII byte string. 428 final byte[] myPidBytes = Integer.toString(Process.myPid()).getBytes(); 429 430 // Concatenate all the policyFiles together on the pipe. 431 final byte[] buffer = new byte[2048]; 432 for (AssetFileDescriptor policyFile : policyFiles) { 433 if (policyFile == null) 434 continue; 435 436 final FileInputStream policyStream = policyFile.createInputStream(); 437 while (true) { 438 int bytesRead = policyStream.read(buffer); 439 if (bytesRead == -1) 440 break; 441 442 // Replace the literal '$' with our PID. This allows us to lock down 443 // certain syscalls that take a pid/tgid. 444 for (int i = 0; i < bytesRead; i++) { 445 if (buffer[i] == '$') { 446 outputStream.write(myPidBytes); 447 } else { 448 outputStream.write(buffer[i]); 449 } 450 } 451 } 452 policyStream.close(); 453 } 454 outputStream.close(); 455 456 return nativeInstallTestFilter(pipe[0].detachFd()); 457 } catch (IOException e) { 458 throw new RuntimeException("Failed to load policy file", e); 459 } 460 } 461 462 /** 463 * Returns the architecture name policy file substring. 464 */ getPolicyAbiString()465 private static native String getPolicyAbiString(); 466 467 /** 468 * Runs the seccomp_bpf_unittest of the given name. 469 */ runKernelUnitTest(final String name)470 private native boolean runKernelUnitTest(final String name); 471 472 /** 473 * Installs a Minijail seccomp policy from a FD. This takes ownership of 474 * the FD and closes it. 475 */ nativeInstallTestFilter(int policyFd)476 private native static boolean nativeInstallTestFilter(int policyFd); 477 478 /** 479 * Attempts to get the CLOCK_BOOTTIME, which is a violation of the 480 * policy specified by installTestFilter(). 481 */ getClockBootTime()482 private native static int getClockBootTime(); 483 } 484