Home | History | Annotate | Download | only in cts
      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.
     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 
     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 
     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 
    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 
    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 
    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      */
    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      */
    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      */
    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 
    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
    289         public void onServiceConnected(ComponentName name, IBinder service) {
    290             set(ISeccompIsolatedService.Stub.asInterface(service));
    291         }
    292 
    293         @Override
    294         public void onServiceDisconnected(ComponentName name) {
    295         }
    296 
    297         @Override
    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
    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      */
    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      */
    465     private static native String getPolicyAbiString();
    466 
    467     /**
    468      * Runs the seccomp_bpf_unittest of the given name.
    469      */
    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      */
    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      */
    482     private native static int getClockBootTime();
    483 }
    484