BlockGuard.java revision 996bf79565ac88402920bd826d6f85952c83be20
1/*
2 * Copyright (C) 2010 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
17package dalvik.system;
18
19import java.io.FileDescriptor;
20import java.io.FileNotFoundException;
21import java.io.IOException;
22import java.net.DatagramPacket;
23import java.net.InetAddress;
24import java.net.SocketException;
25import java.net.SocketImpl;
26import java.net.SocketOptions;
27import libcore.io.Libcore;
28import libcore.io.StructLinger;
29import org.apache.harmony.luni.platform.INetworkSystem;
30import static libcore.io.OsConstants.*;
31
32/**
33 * Mechanism to let threads set restrictions on what code is allowed
34 * to do in their thread.
35 *
36 * <p>This is meant for applications to prevent certain blocking
37 * operations from running on their main event loop (or "UI") threads.
38 *
39 * <p>Note that this is all best-effort to catch most accidental mistakes
40 * and isn't intended to be a perfect mechanism, nor provide any sort of
41 * security.
42 *
43 * @hide
44 */
45public final class BlockGuard {
46
47    public static final int DISALLOW_DISK_WRITE = 0x01;
48    public static final int DISALLOW_DISK_READ = 0x02;
49    public static final int DISALLOW_NETWORK = 0x04;
50    public static final int PASS_RESTRICTIONS_VIA_RPC = 0x08;
51    public static final int PENALTY_LOG = 0x10;
52    public static final int PENALTY_DIALOG = 0x20;
53    public static final int PENALTY_DEATH = 0x40;
54
55    public interface Policy {
56        /**
57         * Called on disk writes.
58         */
59        void onWriteToDisk();
60
61        /**
62         * Called on disk writes.
63         */
64        void onReadFromDisk();
65
66        /**
67         * Called on network operations.
68         */
69        void onNetwork();
70
71        /**
72         * Returns the policy bitmask, for shipping over Binder calls
73         * to remote threads/processes and reinstantiating the policy
74         * there.  The bits in the mask are from the DISALLOW_* and
75         * PENALTY_* constants.
76         */
77        int getPolicyMask();
78    }
79
80    public static class BlockGuardPolicyException extends RuntimeException {
81        // bitmask of DISALLOW_*, PENALTY_*, etc flags
82        private final int mPolicyState;
83        private final int mPolicyViolated;
84        private final String mMessage;   // may be null
85
86        public BlockGuardPolicyException(int policyState, int policyViolated) {
87            this(policyState, policyViolated, null);
88        }
89
90        public BlockGuardPolicyException(int policyState, int policyViolated, String message) {
91            mPolicyState = policyState;
92            mPolicyViolated = policyViolated;
93            mMessage = message;
94            fillInStackTrace();
95        }
96
97        public int getPolicy() {
98            return mPolicyState;
99        }
100
101        public int getPolicyViolation() {
102            return mPolicyViolated;
103        }
104
105        public String getMessage() {
106            // Note: do not change this format casually.  It's
107            // somewhat unfortunately Parceled and passed around
108            // Binder calls and parsed back into an Exception by
109            // Android's StrictMode.  This was the least invasive
110            // option and avoided a gross mix of Java Serialization
111            // combined with Parcels.
112            return "policy=" + mPolicyState + " violation=" + mPolicyViolated +
113                    (mMessage == null ? "" : (" msg=" + mMessage));
114        }
115    }
116
117    /**
118     * The default, permissive policy that doesn't prevent any operations.
119     */
120    public static final Policy LAX_POLICY = new Policy() {
121            public void onWriteToDisk() {}
122            public void onReadFromDisk() {}
123            public void onNetwork() {}
124            public int getPolicyMask() {
125                return 0;
126            }
127        };
128
129    private static ThreadLocal<Policy> threadPolicy = new ThreadLocal<Policy>() {
130        @Override protected Policy initialValue() {
131            return LAX_POLICY;
132        }
133    };
134
135    /**
136     * Get the current thread's policy.
137     *
138     * @return the current thread's policy.  Never returns null.
139     *     Will return the LAX_POLICY instance if nothing else is set.
140     */
141    public static Policy getThreadPolicy() {
142        return threadPolicy.get();
143    }
144
145    /**
146     * Sets the current thread's block guard policy.
147     *
148     * @param policy policy to set.  May not be null.  Use the public LAX_POLICY
149     *   if you want to unset the active policy.
150     */
151    public static void setThreadPolicy(Policy policy) {
152        if (policy == null) {
153            throw new NullPointerException("policy == null");
154        }
155        threadPolicy.set(policy);
156    }
157
158    private BlockGuard() {}
159
160    /**
161     * A network wrapper that calls the policy check functions.
162     */
163    public static class WrappedNetworkSystem implements INetworkSystem {
164        private final INetworkSystem mNetwork;
165
166        public WrappedNetworkSystem(INetworkSystem network) {
167            mNetwork = network;
168        }
169
170        public void accept(FileDescriptor serverFd, SocketImpl newSocket,
171                FileDescriptor clientFd) throws IOException {
172            BlockGuard.getThreadPolicy().onNetwork();
173            mNetwork.accept(serverFd, newSocket, clientFd);
174        }
175
176        public int read(FileDescriptor aFD, byte[] data, int offset, int count) throws IOException {
177            BlockGuard.getThreadPolicy().onNetwork();
178            return mNetwork.read(aFD, data, offset, count);
179        }
180
181        public int readDirect(FileDescriptor aFD, int address, int count) throws IOException {
182            BlockGuard.getThreadPolicy().onNetwork();
183            return mNetwork.readDirect(aFD, address, count);
184        }
185
186        public int write(FileDescriptor fd, byte[] data, int offset, int count)
187                throws IOException {
188            BlockGuard.getThreadPolicy().onNetwork();
189            return mNetwork.write(fd, data, offset, count);
190        }
191
192        public int writeDirect(FileDescriptor fd, int address, int offset, int count)
193                throws IOException {
194            BlockGuard.getThreadPolicy().onNetwork();
195            return mNetwork.writeDirect(fd, address, offset, count);
196        }
197
198        public boolean isConnected(FileDescriptor fd, int timeout) throws IOException {
199            if (timeout != 0) {
200                // Greater than 0 is a timeout, but zero means "poll and return immediately".
201                BlockGuard.getThreadPolicy().onNetwork();
202            }
203            return mNetwork.isConnected(fd, timeout);
204        }
205
206        public int send(FileDescriptor fd, byte[] data, int offset, int length,
207                int port, InetAddress inetAddress) throws IOException {
208            // Note: no BlockGuard violation.  We permit datagrams
209            // without hostname lookups.  (short, bounded amount of time)
210            return mNetwork.send(fd, data, offset, length, port, inetAddress);
211        }
212
213        public int sendDirect(FileDescriptor fd, int address, int offset, int length,
214                int port, InetAddress inetAddress) throws IOException {
215            // Note: no BlockGuard violation.  We permit datagrams
216            // without hostname lookups.  (short, bounded amount of time)
217            return mNetwork.sendDirect(fd, address, offset, length, port, inetAddress);
218        }
219
220        public int recv(FileDescriptor fd, DatagramPacket packet, byte[] data, int offset,
221                int length, boolean peek, boolean connected) throws IOException {
222            BlockGuard.getThreadPolicy().onNetwork();
223            return mNetwork.recv(fd, packet, data, offset, length, peek, connected);
224        }
225
226        public int recvDirect(FileDescriptor fd, DatagramPacket packet, int address, int offset,
227                int length, boolean peek, boolean connected) throws IOException {
228            BlockGuard.getThreadPolicy().onNetwork();
229            return mNetwork.recvDirect(fd, packet, address, offset, length, peek, connected);
230        }
231
232        public void disconnectDatagram(FileDescriptor aFD) throws SocketException {
233            mNetwork.disconnectDatagram(aFD);
234        }
235
236        public void sendUrgentData(FileDescriptor fd, byte value) {
237            mNetwork.sendUrgentData(fd, value);
238        }
239
240        public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs,
241                int numReadable, int numWritable, long timeout, int[] flags)
242                throws SocketException {
243            BlockGuard.getThreadPolicy().onNetwork();
244            return mNetwork.select(readFDs, writeFDs, numReadable, numWritable, timeout, flags);
245        }
246
247        public void close(FileDescriptor aFD) throws IOException {
248            // We exclude sockets without SO_LINGER so that apps can close their network connections
249            // in methods like onDestroy, which will run on the UI thread, without jumping through
250            // extra hoops.
251            if (isLingerSocket(aFD)) {
252                BlockGuard.getThreadPolicy().onNetwork();
253            }
254            mNetwork.close(aFD);
255        }
256
257        private boolean isLingerSocket(FileDescriptor fd) throws SocketException {
258            try {
259                StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER);
260                return linger.isOn() && linger.l_linger > 0;
261            } catch (Exception ignored) {
262                // We're called via Socket.close (which doesn't ask for us to be called), so we
263                // must not throw here, because Socket.close must not throw if asked to close an
264                // already-closed socket.
265                return false;
266            }
267        }
268    }
269}
270