| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.os.cts; |
| |
| import android.app.Service; |
| import android.content.Context; |
| import android.content.ComponentName; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.Environment; |
| import android.os.IBinder; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.os.MemoryFile; |
| import android.os.SystemClock; |
| import android.os.Build; |
| import android.util.Log; |
| import android.test.AndroidTestCase; |
| |
| import com.google.common.util.concurrent.AbstractFuture; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.Date; |
| |
| public class SeccompTest extends AndroidTestCase { |
| final static String TAG = "SeccompTest"; |
| |
| static { |
| System.loadLibrary("ctsos_jni"); |
| } |
| |
| public void testSeccomp() { |
| if (OSFeatures.needsSeccompSupport()) { |
| assertTrue("Please enable seccomp support " |
| + "in your kernel (CONFIG_SECCOMP_FILTER=y)", |
| OSFeatures.hasSeccompSupport()); |
| } |
| } |
| |
| public void testKernelBasicTests() { |
| if (!OSFeatures.needsSeccompSupport()) |
| return; |
| |
| final String[] tests = { |
| "global.mode_strict_support", |
| "global.mode_strict_cannot_call_prctl", |
| "global.no_new_privs_support", |
| "global.mode_filter_support", |
| /* "global.mode_filter_without_nnp", // all Android processes already have nnp */ |
| "global.filter_size_limits", |
| "global.filter_chain_limits", |
| "global.mode_filter_cannot_move_to_strict", |
| "global.mode_filter_get_seccomp", |
| "global.ALLOW_all", |
| "global.empty_prog", |
| "global.unknown_ret_is_kill_inside", |
| "global.unknown_ret_is_kill_above_allow", |
| "global.KILL_all", |
| "global.KILL_one", |
| "global.KILL_one_arg_one", |
| "global.KILL_one_arg_six", |
| "global.arg_out_of_range", |
| "global.ERRNO_one", |
| "global.ERRNO_one_ok", |
| }; |
| runKernelUnitTestSuite(tests); |
| } |
| |
| public void testKernelTrapTests() { |
| if (!OSFeatures.needsSeccompSupport()) |
| return; |
| |
| final String[] tests = { |
| "TRAP.dfl", |
| "TRAP.ign", |
| "TRAP.handler", |
| }; |
| runKernelUnitTestSuite(tests); |
| } |
| |
| public void testKernelPrecedenceTests() { |
| if (!OSFeatures.needsSeccompSupport()) |
| return; |
| |
| final String[] tests = { |
| "precedence.allow_ok", |
| "precedence.kill_is_highest", |
| "precedence.kill_is_highest_in_any_order", |
| "precedence.trap_is_second", |
| "precedence.trap_is_second_in_any_order", |
| "precedence.errno_is_third", |
| "precedence.errno_is_third_in_any_order", |
| "precedence.trace_is_fourth", |
| "precedence.trace_is_fourth_in_any_order", |
| }; |
| runKernelUnitTestSuite(tests); |
| } |
| |
| /* // The SECCOMP_RET_TRACE does not work under Android Arm32. |
| public void testKernelTraceTests() { |
| if (!OSFeatures.needsSeccompSupport()) |
| return; |
| |
| final String[] tests = { |
| "TRACE_poke.read_has_side_effects", |
| "TRACE_poke.getpid_runs_normally", |
| "TRACE_syscall.syscall_allowed", |
| "TRACE_syscall.syscall_redirected", |
| "TRACE_syscall.syscall_dropped", |
| }; |
| runKernelUnitTestSuite(tests); |
| } |
| */ |
| |
| public void testKernelTSYNCTests() { |
| if (!OSFeatures.needsSeccompSupport()) |
| return; |
| |
| final String[] tests = { |
| "global.seccomp_syscall", |
| "global.seccomp_syscall_mode_lock", |
| "global.TSYNC_first", |
| "TSYNC.siblings_fail_prctl", |
| "TSYNC.two_siblings_with_ancestor", |
| /* "TSYNC.two_sibling_want_nnp", // all Android processes already have nnp */ |
| "TSYNC.two_siblings_with_no_filter", |
| "TSYNC.two_siblings_with_one_divergence", |
| "TSYNC.two_siblings_not_under_filter", |
| /* "global.syscall_restart", // ptrace attach fails */ |
| }; |
| runKernelUnitTestSuite(tests); |
| } |
| |
| /** |
| * Runs a kernel unit test suite (an array of kernel test names). |
| */ |
| private void runKernelUnitTestSuite(final String[] tests) { |
| for (final String test : tests) { |
| // TODO: Replace the URL with the documentation when it's finished. |
| assertTrue(test + " failed. This test requires kernel functionality to pass. " |
| + "Please go to http://XXXXX for instructions on how to enable or " |
| + "backport the required functionality.", |
| runKernelUnitTest(test)); |
| } |
| } |
| |
| /** |
| * Integration test for seccomp-bpf policy applied to an isolatedProcess=true |
| * service. This will perform various operations in an isolated process under a |
| * fairly restrictive seccomp policy. |
| */ |
| public void testIsolatedServicePolicy() throws InterruptedException, ExecutionException, |
| RemoteException { |
| if (!OSFeatures.needsSeccompSupport()) |
| return; |
| |
| final IsolatedServiceConnection peer = new IsolatedServiceConnection(); |
| final Intent intent = new Intent(getContext(), IsolatedService.class); |
| assertTrue(getContext().bindService(intent, peer, Context.BIND_AUTO_CREATE)); |
| |
| final ISeccompIsolatedService service = peer.get(); |
| |
| // installFilter() must be called first, to set the seccomp policy. |
| assertTrue(service.installFilter()); |
| assertTrue(service.createThread()); |
| assertTrue(service.getSystemInfo()); |
| doFileWriteTest(service); |
| assertTrue(service.openAshmem()); |
| assertTrue(service.openDevFile()); |
| |
| getContext().unbindService(peer); |
| } |
| |
| /** |
| * Integration test for seccomp-bpf policy with isolatedProcess, where the |
| * process then violates the policy and gets killed by the kernel. |
| */ |
| public void testViolateIsolatedServicePolicy() throws InterruptedException, |
| ExecutionException, RemoteException { |
| if (!OSFeatures.needsSeccompSupport()) |
| return; |
| |
| final IsolatedServiceConnection peer = new IsolatedServiceConnection(); |
| final Intent intent = new Intent(getContext(), IsolatedService.class); |
| assertTrue(getContext().bindService(intent, peer, Context.BIND_AUTO_CREATE)); |
| |
| final ISeccompIsolatedService service = peer.get(); |
| |
| assertTrue(service.installFilter()); |
| boolean gotRemoteException = false; |
| try { |
| service.violatePolicy(); |
| } catch (RemoteException e) { |
| gotRemoteException = true; |
| } |
| assertTrue(gotRemoteException); |
| |
| getContext().unbindService(peer); |
| } |
| |
| private void doFileWriteTest(ISeccompIsolatedService service) throws RemoteException { |
| final String fileName = "seccomp_test"; |
| ParcelFileDescriptor fd = null; |
| try { |
| FileOutputStream fOut = getContext().openFileOutput(fileName, 0); |
| fd = ParcelFileDescriptor.dup(fOut.getFD()); |
| fOut.close(); |
| } catch (FileNotFoundException e) { |
| fail(e.getMessage()); |
| return; |
| } catch (IOException e) { |
| fail(e.getMessage()); |
| return; |
| } |
| |
| assertTrue(service.writeToFile(fd)); |
| |
| try { |
| FileInputStream fIn = getContext().openFileInput(fileName); |
| assertEquals('!', fIn.read()); |
| fIn.close(); |
| } catch (FileNotFoundException e) { |
| fail(e.getMessage()); |
| } catch (IOException e) { |
| fail(e.getMessage()); |
| } |
| } |
| |
| class IsolatedServiceConnection extends AbstractFuture<ISeccompIsolatedService> |
| implements ServiceConnection { |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| set(ISeccompIsolatedService.Stub.asInterface(service)); |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| } |
| |
| @Override |
| public ISeccompIsolatedService get() throws InterruptedException, ExecutionException { |
| try { |
| return get(10, TimeUnit.SECONDS); |
| } catch (TimeoutException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| |
| public static class IsolatedService extends Service { |
| private final ISeccompIsolatedService.Stub mService = new ISeccompIsolatedService.Stub() { |
| public boolean installFilter() { |
| return installTestFilter(); |
| } |
| |
| public boolean createThread() { |
| Thread thread = new Thread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Thread.currentThread().setPriority(Thread.MIN_PRIORITY); |
| Thread.sleep(100); |
| } catch (InterruptedException e) { |
| } |
| } |
| }); |
| thread.run(); |
| try { |
| thread.join(); |
| } catch (InterruptedException e) { |
| return false; |
| } |
| return true; |
| } |
| |
| public boolean getSystemInfo() { |
| long uptimeMillis = SystemClock.uptimeMillis(); |
| if (uptimeMillis < 1) { |
| Log.d(TAG, "SystemClock failed"); |
| return false; |
| } |
| |
| String version = Build.VERSION.CODENAME; |
| if (version.length() == 0) { |
| Log.d(TAG, "Build.VERSION failed"); |
| return false; |
| } |
| |
| long time = (new Date()).getTime(); |
| if (time < 100) { |
| Log.d(TAG, "getTime failed"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| public boolean writeToFile(ParcelFileDescriptor fd) { |
| FileOutputStream fOut = new FileOutputStream(fd.getFileDescriptor()); |
| try { |
| fOut.write('!'); |
| fOut.close(); |
| } catch (IOException e) { |
| return false; |
| } |
| return true; |
| } |
| |
| public boolean openAshmem() { |
| byte[] buffer = {'h', 'e', 'l', 'l', 'o'}; |
| try { |
| MemoryFile file = new MemoryFile("seccomp_isolated_test", 32); |
| file.writeBytes(buffer, 0, 0, buffer.length); |
| file.close(); |
| return true; |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| |
| public boolean openDevFile() { |
| try { |
| FileInputStream fIn = new FileInputStream("/dev/zero"); |
| boolean succeed = fIn.read() == 0; |
| succeed &= fIn.read() == 0; |
| succeed &= fIn.read() == 0; |
| fIn.close(); |
| return succeed; |
| } catch (FileNotFoundException e) { |
| return false; |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| |
| public void violatePolicy() { |
| getClockBootTime(); |
| } |
| }; |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| return mService; |
| } |
| } |
| |
| /** |
| * Runs the seccomp_bpf_unittest of the given name. |
| */ |
| private native boolean runKernelUnitTest(final String name); |
| |
| /** |
| * Installs a test seccomp-bpf filter program that. |
| */ |
| private native static boolean installTestFilter(); |
| |
| /** |
| * Attempts to get the CLOCK_BOOTTIME, which is a violation of the |
| * policy specified by installTestFilter(). |
| */ |
| private native static int getClockBootTime(); |
| } |