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