• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.server.slice;
16 
17 import android.content.ContentProvider;
18 import android.content.Context;
19 import android.net.Uri;
20 import android.os.Environment;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.os.Message;
24 import android.text.format.DateUtils;
25 import android.util.ArrayMap;
26 import android.util.ArraySet;
27 import android.util.AtomicFile;
28 import android.util.Log;
29 import android.util.Slog;
30 import android.util.Xml.Encoding;
31 
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.util.XmlUtils;
35 import com.android.server.slice.SliceProviderPermissions.SliceAuthority;
36 
37 import org.xmlpull.v1.XmlPullParser;
38 import org.xmlpull.v1.XmlPullParserException;
39 import org.xmlpull.v1.XmlPullParserFactory;
40 import org.xmlpull.v1.XmlSerializer;
41 
42 import java.io.File;
43 import java.io.FileNotFoundException;
44 import java.io.FileOutputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.util.Objects;
48 
49 public class SlicePermissionManager implements DirtyTracker {
50 
51     private static final String TAG = "SlicePermissionManager";
52 
53     /**
54      * The amount of time we'll cache a SliceProviderPermissions or SliceClientPermissions
55      * in case they are used again.
56      */
57     private static final long PERMISSION_CACHE_PERIOD = 5 * DateUtils.MINUTE_IN_MILLIS;
58 
59     /**
60      * The amount of time we delay flushing out permission changes to disk because they usually
61      * come in short bursts.
62      */
63     private static final long WRITE_GRACE_PERIOD = 500;
64 
65     private static final String SLICE_DIR = "slice";
66 
67     // If/when this bumps again we'll need to write it out in the disk somewhere.
68     // Currently we don't have a central file for this in version 2 and there is no
69     // reason to add one until we actually have incompatible version bumps.
70     // This does however block us from reading backups from P-DP1 which may contain
71     // a very different XML format for perms.
72     static final int DB_VERSION = 2;
73 
74     private static final String TAG_LIST = "slice-access-list";
75     private final String ATT_VERSION = "version";
76 
77     private final File mSliceDir;
78     private final Context mContext;
79     private final Handler mHandler;
80     @GuardedBy("itself")
81     private final ArrayMap<PkgUser, SliceProviderPermissions> mCachedProviders = new ArrayMap<>();
82     @GuardedBy("itself")
83     private final ArrayMap<PkgUser, SliceClientPermissions> mCachedClients = new ArrayMap<>();
84     @GuardedBy("this")
85     private final ArraySet<Persistable> mDirty = new ArraySet<>();
86 
87     @VisibleForTesting
SlicePermissionManager(Context context, Looper looper, File sliceDir)88     SlicePermissionManager(Context context, Looper looper, File sliceDir) {
89         mContext = context;
90         mHandler = new H(looper);
91         mSliceDir = sliceDir;
92     }
93 
SlicePermissionManager(Context context, Looper looper)94     public SlicePermissionManager(Context context, Looper looper) {
95         this(context, looper, new File(Environment.getDataDirectory(), "system/" + SLICE_DIR));
96     }
97 
grantFullAccess(String pkg, int userId)98     public void grantFullAccess(String pkg, int userId) {
99         PkgUser pkgUser = new PkgUser(pkg, userId);
100         SliceClientPermissions client = getClient(pkgUser);
101         client.setHasFullAccess(true);
102     }
103 
grantSliceAccess(String pkg, int userId, String providerPkg, int providerUser, Uri uri)104     public void grantSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
105             Uri uri) {
106         PkgUser pkgUser = new PkgUser(pkg, userId);
107         PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
108 
109         SliceClientPermissions client = getClient(pkgUser);
110         client.grantUri(uri, providerPkgUser);
111 
112         SliceProviderPermissions provider = getProvider(providerPkgUser);
113         provider.getOrCreateAuthority(ContentProvider.getUriWithoutUserId(uri).getAuthority())
114                 .addPkg(pkgUser);
115     }
116 
revokeSliceAccess(String pkg, int userId, String providerPkg, int providerUser, Uri uri)117     public void revokeSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
118             Uri uri) {
119         PkgUser pkgUser = new PkgUser(pkg, userId);
120         PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
121 
122         SliceClientPermissions client = getClient(pkgUser);
123         client.revokeUri(uri, providerPkgUser);
124     }
125 
removePkg(String pkg, int userId)126     public void removePkg(String pkg, int userId) {
127         PkgUser pkgUser = new PkgUser(pkg, userId);
128         SliceProviderPermissions provider = getProvider(pkgUser);
129 
130         for (SliceAuthority authority : provider.getAuthorities()) {
131             for (PkgUser p : authority.getPkgs()) {
132                 getClient(p).removeAuthority(authority.getAuthority(), userId);
133             }
134         }
135         SliceClientPermissions client = getClient(pkgUser);
136         client.clear();
137         mHandler.obtainMessage(H.MSG_REMOVE, pkgUser).sendToTarget();
138     }
139 
getAllPackagesGranted(String pkg)140     public String[] getAllPackagesGranted(String pkg) {
141         ArraySet<String> ret = new ArraySet<>();
142         for (SliceAuthority authority : getProvider(new PkgUser(pkg, 0)).getAuthorities()) {
143             for (PkgUser pkgUser : authority.getPkgs()) {
144                 ret.add(pkgUser.mPkg);
145             }
146         }
147         return ret.toArray(new String[ret.size()]);
148     }
149 
hasFullAccess(String pkg, int userId)150     public boolean hasFullAccess(String pkg, int userId) {
151         PkgUser pkgUser = new PkgUser(pkg, userId);
152         return getClient(pkgUser).hasFullAccess();
153     }
154 
hasPermission(String pkg, int userId, Uri uri)155     public boolean hasPermission(String pkg, int userId, Uri uri) {
156         PkgUser pkgUser = new PkgUser(pkg, userId);
157         SliceClientPermissions client = getClient(pkgUser);
158         int providerUserId = ContentProvider.getUserIdFromUri(uri, userId);
159         return client.hasFullAccess()
160                 || client.hasPermission(ContentProvider.getUriWithoutUserId(uri), providerUserId);
161     }
162 
163     @Override
onPersistableDirty(Persistable obj)164     public void onPersistableDirty(Persistable obj) {
165         mHandler.removeMessages(H.MSG_PERSIST);
166         mHandler.obtainMessage(H.MSG_ADD_DIRTY, obj).sendToTarget();
167         mHandler.sendEmptyMessageDelayed(H.MSG_PERSIST, WRITE_GRACE_PERIOD);
168     }
169 
writeBackup(XmlSerializer out)170     public void writeBackup(XmlSerializer out) throws IOException, XmlPullParserException {
171         synchronized (this) {
172             out.startTag(null, TAG_LIST);
173             out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
174 
175             // Don't do anything with changes from the backup, because there shouldn't be any.
176             DirtyTracker tracker = obj -> { };
177             if (mHandler.hasMessages(H.MSG_PERSIST)) {
178                 mHandler.removeMessages(H.MSG_PERSIST);
179                 handlePersist();
180             }
181             for (String file : new File(mSliceDir.getAbsolutePath()).list()) {
182                 try (ParserHolder parser = getParser(file)) {
183                     Persistable p = null;
184                     while (parser.parser.getEventType() != XmlPullParser.END_DOCUMENT) {
185                         if (parser.parser.getEventType() == XmlPullParser.START_TAG) {
186                             if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) {
187                                 p = SliceClientPermissions.createFrom(parser.parser, tracker);
188                             } else {
189                                 p = SliceProviderPermissions.createFrom(parser.parser, tracker);
190                             }
191                             break;
192                         }
193                         parser.parser.next();
194                     }
195                     if (p != null) {
196                         p.writeTo(out);
197                     } else {
198                         Slog.w(TAG, "Invalid or empty slice permissions file: " + file);
199                     }
200                 }
201             }
202 
203             out.endTag(null, TAG_LIST);
204         }
205     }
206 
readRestore(XmlPullParser parser)207     public void readRestore(XmlPullParser parser) throws IOException, XmlPullParserException {
208         synchronized (this) {
209             while ((parser.getEventType() != XmlPullParser.START_TAG
210                     || !TAG_LIST.equals(parser.getName()))
211                     && parser.getEventType() != XmlPullParser.END_DOCUMENT) {
212                 parser.next();
213             }
214             int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
215             if (xmlVersion < DB_VERSION) {
216                 // No conversion support right now.
217                 return;
218             }
219             while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
220                 if (parser.getEventType() == XmlPullParser.START_TAG) {
221                     if (SliceClientPermissions.TAG_CLIENT.equals(parser.getName())) {
222                         SliceClientPermissions client = SliceClientPermissions.createFrom(parser,
223                                 this);
224                         synchronized (mCachedClients) {
225                             mCachedClients.put(client.getPkg(), client);
226                         }
227                         onPersistableDirty(client);
228                         mHandler.sendMessageDelayed(
229                                 mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, client.getPkg()),
230                                 PERMISSION_CACHE_PERIOD);
231                     } else if (SliceProviderPermissions.TAG_PROVIDER.equals(parser.getName())) {
232                         SliceProviderPermissions provider = SliceProviderPermissions.createFrom(
233                                 parser, this);
234                         synchronized (mCachedProviders) {
235                             mCachedProviders.put(provider.getPkg(), provider);
236                         }
237                         onPersistableDirty(provider);
238                         mHandler.sendMessageDelayed(
239                                 mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, provider.getPkg()),
240                                 PERMISSION_CACHE_PERIOD);
241                     } else {
242                         parser.next();
243                     }
244                 } else {
245                     parser.next();
246                 }
247             }
248         }
249     }
250 
getClient(PkgUser pkgUser)251     private SliceClientPermissions getClient(PkgUser pkgUser) {
252         SliceClientPermissions client;
253         synchronized (mCachedClients) {
254             client = mCachedClients.get(pkgUser);
255         }
256         if (client == null) {
257             try (ParserHolder parser = getParser(SliceClientPermissions.getFileName(pkgUser))) {
258                 client = SliceClientPermissions.createFrom(parser.parser, this);
259                 synchronized (mCachedClients) {
260                     mCachedClients.put(pkgUser, client);
261                 }
262                 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, pkgUser),
263                         PERMISSION_CACHE_PERIOD);
264                 return client;
265             } catch (FileNotFoundException e) {
266                 // No client exists yet.
267             } catch (IOException e) {
268                 Log.e(TAG, "Can't read client", e);
269             } catch (XmlPullParserException e) {
270                 Log.e(TAG, "Can't read client", e);
271             }
272             // Can't read or no permissions exist, create a clean object.
273             client = new SliceClientPermissions(pkgUser, this);
274             synchronized (mCachedClients) {
275                 mCachedClients.put(pkgUser, client);
276             }
277         }
278         return client;
279     }
280 
getProvider(PkgUser pkgUser)281     private SliceProviderPermissions getProvider(PkgUser pkgUser) {
282         SliceProviderPermissions provider;
283         synchronized (mCachedProviders) {
284             provider = mCachedProviders.get(pkgUser);
285         }
286         if (provider == null) {
287             try (ParserHolder parser = getParser(SliceProviderPermissions.getFileName(pkgUser))) {
288                 provider = SliceProviderPermissions.createFrom(parser.parser, this);
289                 synchronized (mCachedProviders) {
290                     mCachedProviders.put(pkgUser, provider);
291                 }
292                 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, pkgUser),
293                         PERMISSION_CACHE_PERIOD);
294                 return provider;
295             } catch (FileNotFoundException e) {
296                 // No provider exists yet.
297             } catch (IOException e) {
298                 Log.e(TAG, "Can't read provider", e);
299             } catch (XmlPullParserException e) {
300                 Log.e(TAG, "Can't read provider", e);
301             }
302             // Can't read or no permissions exist, create a clean object.
303             provider = new SliceProviderPermissions(pkgUser, this);
304             synchronized (mCachedProviders) {
305                 mCachedProviders.put(pkgUser, provider);
306             }
307         }
308         return provider;
309     }
310 
getParser(String fileName)311     private ParserHolder getParser(String fileName)
312             throws FileNotFoundException, XmlPullParserException {
313         AtomicFile file = getFile(fileName);
314         ParserHolder holder = new ParserHolder();
315         holder.input = file.openRead();
316         holder.parser = XmlPullParserFactory.newInstance().newPullParser();
317         holder.parser.setInput(holder.input, Encoding.UTF_8.name());
318         return holder;
319     }
320 
getFile(String fileName)321     private AtomicFile getFile(String fileName) {
322         if (!mSliceDir.exists()) {
323             mSliceDir.mkdir();
324         }
325         return new AtomicFile(new File(mSliceDir, fileName));
326     }
327 
328     @VisibleForTesting
handlePersist()329     void handlePersist() {
330         synchronized (this) {
331             for (Persistable persistable : mDirty) {
332                 AtomicFile file = getFile(persistable.getFileName());
333                 final FileOutputStream stream;
334                 try {
335                     stream = file.startWrite();
336                 } catch (IOException e) {
337                     Slog.w(TAG, "Failed to save access file", e);
338                     return;
339                 }
340 
341                 try {
342                     XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
343                     out.setOutput(stream, Encoding.UTF_8.name());
344 
345                     persistable.writeTo(out);
346 
347                     out.flush();
348                     file.finishWrite(stream);
349                 } catch (IOException | XmlPullParserException | RuntimeException e) {
350                     Slog.w(TAG, "Failed to save access file, restoring backup", e);
351                     file.failWrite(stream);
352                 }
353             }
354             mDirty.clear();
355         }
356     }
357 
358     // use addPersistableDirty(); this is just for tests
359     @VisibleForTesting
addDirtyImmediate(Persistable obj)360     void addDirtyImmediate(Persistable obj) {
361         synchronized (this) {
362             mDirty.add(obj);
363         }
364     }
365 
handleRemove(PkgUser pkgUser)366     private void handleRemove(PkgUser pkgUser) {
367         getFile(SliceClientPermissions.getFileName(pkgUser)).delete();
368         getFile(SliceProviderPermissions.getFileName(pkgUser)).delete();
369         synchronized (this) {
370             synchronized (mCachedClients) {
371                 mDirty.remove(mCachedClients.remove(pkgUser));
372             }
373             synchronized (mCachedProviders) {
374                 mDirty.remove(mCachedProviders.remove(pkgUser));
375             }
376         }
377     }
378 
379     private final class H extends Handler {
380         private static final int MSG_ADD_DIRTY = 1;
381         private static final int MSG_PERSIST = 2;
382         private static final int MSG_REMOVE = 3;
383         private static final int MSG_CLEAR_CLIENT = 4;
384         private static final int MSG_CLEAR_PROVIDER = 5;
385 
H(Looper looper)386         public H(Looper looper) {
387             super(looper);
388         }
389 
390         @Override
handleMessage(Message msg)391         public void handleMessage(Message msg) {
392             switch (msg.what) {
393                 case MSG_ADD_DIRTY:
394                     synchronized (SlicePermissionManager.this) {
395                         mDirty.add((Persistable) msg.obj);
396                     }
397                     break;
398                 case MSG_PERSIST:
399                     handlePersist();
400                     break;
401                 case MSG_REMOVE:
402                     handleRemove((PkgUser) msg.obj);
403                     break;
404                 case MSG_CLEAR_CLIENT:
405                     synchronized (mCachedClients) {
406                         mCachedClients.remove(msg.obj);
407                     }
408                     break;
409                 case MSG_CLEAR_PROVIDER:
410                     synchronized (mCachedProviders) {
411                         mCachedProviders.remove(msg.obj);
412                     }
413                     break;
414             }
415         }
416     }
417 
418     public static class PkgUser {
419         private static final String SEPARATOR = "@";
420         private static final String FORMAT = "%s" + SEPARATOR + "%d";
421         private final String mPkg;
422         private final int mUserId;
423 
PkgUser(String pkg, int userId)424         public PkgUser(String pkg, int userId) {
425             mPkg = pkg;
426             mUserId = userId;
427         }
428 
PkgUser(String pkgUserStr)429         public PkgUser(String pkgUserStr) throws IllegalArgumentException {
430             try {
431                 String[] vals = pkgUserStr.split(SEPARATOR, 2);
432                 mPkg = vals[0];
433                 mUserId = Integer.parseInt(vals[1]);
434             } catch (Exception e) {
435                 throw new IllegalArgumentException(e);
436             }
437         }
438 
getPkg()439         public String getPkg() {
440             return mPkg;
441         }
442 
getUserId()443         public int getUserId() {
444             return mUserId;
445         }
446 
447         @Override
hashCode()448         public int hashCode() {
449             return mPkg.hashCode() + mUserId;
450         }
451 
452         @Override
equals(Object obj)453         public boolean equals(Object obj) {
454             if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
455             PkgUser other = (PkgUser) obj;
456             return Objects.equals(other.mPkg, mPkg) && other.mUserId == mUserId;
457         }
458 
459         @Override
toString()460         public String toString() {
461             return String.format(FORMAT, mPkg, mUserId);
462         }
463     }
464 
465     private class ParserHolder implements AutoCloseable {
466 
467         private InputStream input;
468         private XmlPullParser parser;
469 
470         @Override
close()471         public void close() throws IOException {
472             input.close();
473         }
474     }
475 }
476