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