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