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