• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 com.android.tools.lint.checks;
18 
19 import static com.android.tools.lint.detector.api.LintConstants.ANDROID_MANIFEST_XML;
20 import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI;
21 import static com.android.tools.lint.detector.api.LintConstants.ATTR_EXPORTED;
22 import static com.android.tools.lint.detector.api.LintConstants.ATTR_PATH;
23 import static com.android.tools.lint.detector.api.LintConstants.ATTR_PATH_PATTERN;
24 import static com.android.tools.lint.detector.api.LintConstants.ATTR_PATH_PREFIX;
25 import static com.android.tools.lint.detector.api.LintConstants.ATTR_PERMISSION;
26 import static com.android.tools.lint.detector.api.LintConstants.ATTR_READ_PERMISSION;
27 import static com.android.tools.lint.detector.api.LintConstants.ATTR_WRITE_PERMISSION;
28 import static com.android.tools.lint.detector.api.LintConstants.TAG_APPLICATION;
29 import static com.android.tools.lint.detector.api.LintConstants.TAG_GRANT_PERMISSION;
30 import static com.android.tools.lint.detector.api.LintConstants.TAG_INTENT_FILTER;
31 import static com.android.tools.lint.detector.api.LintConstants.TAG_PATH_PERMISSION;
32 import static com.android.tools.lint.detector.api.LintConstants.TAG_PROVIDER;
33 import static com.android.tools.lint.detector.api.LintConstants.TAG_SERVICE;
34 
35 import com.android.tools.lint.detector.api.Category;
36 import com.android.tools.lint.detector.api.Context;
37 import com.android.tools.lint.detector.api.Detector;
38 import com.android.tools.lint.detector.api.Issue;
39 import com.android.tools.lint.detector.api.JavaContext;
40 import com.android.tools.lint.detector.api.LintUtils;
41 import com.android.tools.lint.detector.api.Location;
42 import com.android.tools.lint.detector.api.Scope;
43 import com.android.tools.lint.detector.api.Severity;
44 import com.android.tools.lint.detector.api.Speed;
45 import com.android.tools.lint.detector.api.XmlContext;
46 
47 import org.w3c.dom.Attr;
48 import org.w3c.dom.Element;
49 import org.w3c.dom.Node;
50 
51 import java.io.File;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Collection;
55 import java.util.EnumSet;
56 import java.util.Iterator;
57 import java.util.List;
58 
59 import lombok.ast.AstVisitor;
60 import lombok.ast.Expression;
61 import lombok.ast.ForwardingAstVisitor;
62 import lombok.ast.Identifier;
63 import lombok.ast.MethodInvocation;
64 import lombok.ast.StrictListAccessor;
65 
66 /**
67  * Checks that exported services request a permission.
68  */
69 public class SecurityDetector extends Detector implements Detector.XmlScanner,
70         Detector.JavaScanner {
71 
72     /** Exported services */
73     public static final Issue EXPORTED_SERVICE = Issue.create(
74             "ExportedService", //$NON-NLS-1$
75             "Checks for exported services that do not require permissions",
76             "Exported services (services which either set exported=true or contain " +
77             "an intent-filter and do not specify exported=false) should define a " +
78             "permission that an entity must have in order to launch the service " +
79             "or bind to it. Without this, any application can use this service.",
80             Category.SECURITY,
81             5,
82             Severity.WARNING,
83             SecurityDetector.class,
84             EnumSet.of(Scope.MANIFEST));
85 
86     /** Exported content providers */
87     public static final Issue EXPORTED_PROVIDER = Issue.create(
88             "ExportedContentProvider", //$NON-NLS-1$
89             "Checks for exported content providers that do not require permissions",
90             "Content providers are exported by default and any application on the " +
91             "system can potentially use them to read and write data. If the content" +
92             "provider provides access to sensitive data, it should be protected by " +
93             "specifying export=false in the manifest or by protecting it with a " +
94             "permission that can be granted to other applications.",
95             Category.SECURITY,
96             5,
97             Severity.WARNING,
98             SecurityDetector.class,
99             EnumSet.of(Scope.MANIFEST));
100 
101     /** Content provides which grant all URIs access */
102     public static final Issue OPEN_PROVIDER = Issue.create(
103             "GrantAllUris", //$NON-NLS-1$
104             "Checks for <grant-uri-permission> elements where everything is shared",
105             "The <grant-uri-permission> element allows specific paths to be shared. " +
106             "This detector checks for a path URL of just '/' (everything), which is " +
107             "probably not what you want; you should limit access to a subset.",
108             Category.SECURITY,
109             7,
110             Severity.WARNING,
111             SecurityDetector.class,
112             EnumSet.of(Scope.MANIFEST));
113 
114     /** Using the world-writable flag */
115     public static final Issue WORLD_WRITEABLE = Issue.create(
116             "WorldWriteableFiles", //$NON-NLS-1$
117             "Checks for openFileOutput() and getSharedPreferences() calls passing " +
118             "MODE_WORLD_WRITEABLE",
119             "There are cases where it is appropriate for an application to write " +
120             "world writeable files, but these should be reviewed carefully to " +
121             "ensure that they contain no private data, and that if the file is " +
122             "modified by a malicious application it does not trick or compromise " +
123             "your application.",
124             Category.SECURITY,
125             4,
126             Severity.WARNING,
127             SecurityDetector.class,
128             Scope.JAVA_FILE_SCOPE);
129 
130 
131     /** Using the world-readable flag */
132     public static final Issue WORLD_READABLE = Issue.create(
133             "WorldReadableFiles", //$NON-NLS-1$
134             "Checks for openFileOutput() and getSharedPreferences() calls passing " +
135             "MODE_WORLD_READABLE",
136             "There are cases where it is appropriate for an application to write " +
137             "world readable files, but these should be reviewed carefully to " +
138             "ensure that they contain no private data that is leaked to other " +
139             "applications.",
140             Category.SECURITY,
141             4,
142             Severity.WARNING,
143             SecurityDetector.class,
144             Scope.JAVA_FILE_SCOPE);
145 
146     /** Constructs a new {@link SecurityDetector} check */
SecurityDetector()147     public SecurityDetector() {
148     }
149 
150     @Override
getSpeed()151     public Speed getSpeed() {
152         return Speed.FAST;
153     }
154     @Override
appliesTo(Context context, File file)155     public boolean appliesTo(Context context, File file) {
156         return file.getName().equals(ANDROID_MANIFEST_XML);
157     }
158 
159     // ---- Implements Detector.XmlScanner ----
160 
161     @Override
getApplicableElements()162     public Collection<String> getApplicableElements() {
163         return Arrays.asList(
164             TAG_SERVICE,
165             TAG_GRANT_PERMISSION,
166             TAG_PROVIDER
167         );
168     }
169 
170     @Override
visitElement(XmlContext context, Element element)171     public void visitElement(XmlContext context, Element element) {
172         String tag = element.getTagName();
173         if (tag.equals(TAG_SERVICE)) {
174             checkService(context, element);
175         } else if (tag.equals(TAG_GRANT_PERMISSION)) {
176             checkGrantPermission(context, element);
177         } else if (tag.equals(TAG_PROVIDER)) {
178             checkProvider(context, element);
179         }
180     }
181 
checkService(XmlContext context, Element element)182     private void checkService(XmlContext context, Element element) {
183         String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED);
184         boolean exported;
185         if (exportValue != null && exportValue.length() > 0) {
186             exported = Boolean.valueOf(exportValue);
187         } else {
188             boolean haveIntentFilters = false;
189             for (Element child : LintUtils.getChildren(element)) {
190                 if (child.getTagName().equals(TAG_INTENT_FILTER)) {
191                     haveIntentFilters = true;
192                     break;
193                 }
194             }
195             exported = haveIntentFilters;
196         }
197 
198         if (exported) {
199             // Make sure this service has a permission
200             String permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
201             if (permission == null || permission.length() == 0) {
202                 Node parent = element.getParentNode();
203                 if (parent.getNodeType() == Node.ELEMENT_NODE
204                         && parent.getNodeName().equals(TAG_APPLICATION)) {
205                     Element application = (Element) parent;
206                     permission = application.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
207                     if (permission == null || permission.length() == 0) {
208                         // No declared permission for this exported service: complain
209                         context.report(EXPORTED_SERVICE, element,
210                             context.getLocation(element),
211                             "Exported service does not require permission", null);
212                     }
213                 }
214             }
215         }
216     }
217 
checkGrantPermission(XmlContext context, Element element)218     private void checkGrantPermission(XmlContext context, Element element) {
219         Attr path = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH);
220         Attr prefix = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PREFIX);
221         Attr pattern = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PATTERN);
222 
223         String msg = "Content provider shares everything; this is potentially dangerous.";
224         if (path != null && path.getValue().equals("/")) { //$NON-NLS-1$
225             context.report(OPEN_PROVIDER, path, context.getLocation(path), msg, null);
226         }
227         if (prefix != null && prefix.getValue().equals("/")) { //$NON-NLS-1$
228             context.report(OPEN_PROVIDER, prefix, context.getLocation(prefix), msg, null);
229         }
230         if (pattern != null && (pattern.getValue().equals("/")  //$NON-NLS-1$
231                /* || pattern.getValue().equals(".*")*/)) {
232             context.report(OPEN_PROVIDER, pattern, context.getLocation(pattern), msg, null);
233         }
234     }
235 
checkProvider(XmlContext context, Element element)236     private void checkProvider(XmlContext context, Element element) {
237         String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED);
238         // Content providers are exported by default
239         boolean exported = true;
240         if (exportValue != null && exportValue.length() > 0) {
241             exported = Boolean.valueOf(exportValue);
242         }
243 
244         if (exported) {
245             // Just check for some use of permissions. Other Lint checks can check the saneness
246             // of the permissions. We'll accept the permission, readPermission, or writePermission
247             // attributes on the provider element, or a path-permission element.
248             String permission = element.getAttributeNS(ANDROID_URI, ATTR_READ_PERMISSION);
249             if (permission == null || permission.length() == 0) {
250                 permission = element.getAttributeNS(ANDROID_URI, ATTR_WRITE_PERMISSION);
251                 if (permission == null || permission.length() == 0) {
252                     permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
253                     if (permission == null || permission.length() == 0) {
254                         // No permission attributes? Check for path-permission.
255 
256                         // TODO: Add a Lint check to ensure the path-permission is good, similar to
257                         // the grant-uri-permission check.
258                         boolean hasPermission = false;
259                         for (Element child : LintUtils.getChildren(element)) {
260                             String tag = child.getTagName();
261                             if (tag.equals(TAG_PATH_PERMISSION)) {
262                                 hasPermission = true;
263                                 break;
264                             }
265                         }
266 
267                         if (!hasPermission) {
268                             context.report(EXPORTED_PROVIDER, element, context.getLocation(element),
269                                     "Exported content providers can provide access to " +
270                                             "potentially sensitive data",
271                                     null);
272                         }
273                     }
274                 }
275             }
276         }
277     }
278 
279     // ---- Implements Detector.JavaScanner ----
280 
281     @Override
getApplicableMethodNames()282     public List<String> getApplicableMethodNames() {
283         // These are the API calls that can accept a MODE_WORLD_READABLE/MODE_WORLD_WRITABLE
284         // argument.
285         List<String> values = new ArrayList<String>(2);
286         values.add("openFileOutput"); //$NON-NLS-1$
287         values.add("getSharedPreferences"); //$NON-NLS-1$
288         return values;
289     }
290 
291     @Override
visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node)292     public void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node) {
293         StrictListAccessor<Expression,MethodInvocation> args = node.astArguments();
294         Iterator<Expression> iterator = args.iterator();
295         while (iterator.hasNext()) {
296             iterator.next().accept(visitor);
297         }
298     }
299 
300     @Override
createJavaVisitor(JavaContext context)301     public AstVisitor createJavaVisitor(JavaContext context) {
302         return new IdentifierVisitor(context);
303     }
304 
305     private static class IdentifierVisitor extends ForwardingAstVisitor {
306         private final JavaContext mContext;
307 
IdentifierVisitor(JavaContext context)308         public IdentifierVisitor(JavaContext context) {
309             super();
310             this.mContext = context;
311         }
312 
313         @Override
visitIdentifier(Identifier node)314         public boolean visitIdentifier(Identifier node) {
315             if ("MODE_WORLD_WRITEABLE".equals(node.getDescription())) { //$NON-NLS-1$
316                 Location location = mContext.getLocation(node);
317                 mContext.report(WORLD_WRITEABLE, node, location,
318                         "Using MODE_WORLD_WRITEABLE when creating files can be " +
319                                 "risky, review carefully",
320                         null);
321             } else if ("MODE_WORLD_READABLE".equals(node.getDescription())) { //$NON-NLS-1$
322                 Location location = mContext.getLocation(node);
323                 mContext.report(WORLD_READABLE, node, location,
324                         "Using MODE_WORLD_READABLE when creating files can be " +
325                                 "risky, review carefully",
326                         null);
327             }
328 
329             return false;
330         }
331     }
332 }
333