• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.server.om;
18 
19 import static android.content.Context.IDMAP_SERVICE;
20 
21 import static com.android.server.om.OverlayManagerService.TAG;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.os.FabricatedOverlayInfo;
26 import android.os.FabricatedOverlayInternal;
27 import android.os.IBinder;
28 import android.os.IIdmap2;
29 import android.os.OverlayConstraint;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.os.StrictMode;
33 import android.os.SystemClock;
34 import android.os.SystemService;
35 import android.text.TextUtils;
36 import android.util.Slog;
37 
38 import com.android.server.FgThread;
39 
40 import java.io.File;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.List;
44 import java.util.concurrent.TimeoutException;
45 
46 /**
47  * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 seconds
48  * without a transaction.
49  **/
50 class IdmapDaemon {
51     // The amount of time in milliseconds to wait after a transaction to the idmap service is made
52     // before stopping the service.
53     private static final int SERVICE_TIMEOUT_MS = 10000;
54 
55     // The device may enter CPU sleep while waiting for the service startup, and in that mode
56     // the uptime doesn't increment. Thus, we need to have two timeouts: a smaller one for the
57     // uptime and a longer one for the wall time in case when the device never advances the uptime,
58     // so the watchdog won't get triggered.
59 
60     // The amount of uptime in milliseconds to wait when attempting to connect to idmap service.
61     private static final int SERVICE_CONNECT_UPTIME_TIMEOUT_MS = 5000;
62     // The amount of wall time in milliseconds to wait.
63     private static final int SERVICE_CONNECT_WALLTIME_TIMEOUT_MS = 30000;
64     private static final int SERVICE_CONNECT_INTERVAL_SLEEP_MS = 5;
65 
66     private static final String IDMAP_DAEMON = "idmap2d";
67 
68     private static IdmapDaemon sInstance;
69     private volatile IIdmap2 mService;
70     private int mOpenedCount = 0;
71     private final Object mIdmapToken = new Object();
72 
73     /**
74      * An {@link AutoCloseable} connection to the idmap service. When the connection is closed or
75      * finalized, the idmap service will be stopped after a period of time unless another connection
76      * to the service is open.
77      **/
78     private final class Connection implements AutoCloseable {
79         @Nullable
80         private final IIdmap2 mIdmap2;
81         private boolean mOpened = true;
82 
Connection()83         private Connection() {
84             mIdmap2 = null;
85             mOpened = false;
86         }
87 
Connection(@onNull IIdmap2 idmap2)88         private Connection(@NonNull IIdmap2 idmap2) {
89             mIdmap2 = idmap2;
90             synchronized (mIdmapToken) {
91                 ++mOpenedCount;
92             }
93         }
94 
95         @Override
close()96         public void close() {
97             synchronized (mIdmapToken) {
98                 if (!mOpened) {
99                     return;
100                 }
101 
102                 mOpened = false;
103                 if (--mOpenedCount != 0) {
104                     // Only post the callback to stop the service if the service does not have an
105                     // open connection.
106                     return;
107                 }
108 
109                 final var service = mService;
110                 FgThread.getHandler().postDelayed(() -> {
111                     synchronized (mIdmapToken) {
112                         // Only stop the service if it's the one we were scheduled for and
113                         // it does not have an open connection.
114                         if (mService != service || mOpenedCount != 0) {
115                             return;
116                         }
117 
118                         stopIdmapServiceLocked();
119                         mService = null;
120                     }
121                 }, mIdmapToken, SERVICE_TIMEOUT_MS);
122             }
123         }
124 
125         @Nullable
getIdmap2()126         public IIdmap2 getIdmap2() {
127             return mIdmap2;
128         }
129     }
130 
getInstance()131     static IdmapDaemon getInstance() {
132         if (sInstance == null) {
133             sInstance = new IdmapDaemon();
134         }
135         return sInstance;
136     }
137 
createIdmap(@onNull String targetPath, @NonNull String overlayPath, @Nullable String overlayName, int policies, boolean enforce, int userId, @NonNull OverlayConstraint[] constraints)138     String createIdmap(@NonNull String targetPath, @NonNull String overlayPath,
139             @Nullable String overlayName, int policies, boolean enforce, int userId,
140             @NonNull OverlayConstraint[] constraints) throws TimeoutException, RemoteException {
141         try (Connection c = connect()) {
142             final IIdmap2 idmap2 = c.getIdmap2();
143             if (idmap2 == null) {
144                 Slog.w(TAG, "idmap2d service is not ready for createIdmap(\"" + targetPath
145                         + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", "
146                         + enforce + ", " + userId + ")");
147                 return null;
148             }
149 
150             return idmap2.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
151                     policies, enforce, userId, constraints);
152         }
153     }
154 
removeIdmap(String overlayPath, int userId)155     boolean removeIdmap(String overlayPath, int userId) throws TimeoutException, RemoteException {
156         try (Connection c = connect()) {
157             final IIdmap2 idmap2 = c.getIdmap2();
158             if (idmap2 == null) {
159                 Slog.w(TAG, "idmap2d service is not ready for removeIdmap(\"" + overlayPath
160                         + "\", " + userId + ")");
161                 return false;
162             }
163 
164             return idmap2.removeIdmap(overlayPath, userId);
165         }
166     }
167 
verifyIdmap(@onNull String targetPath, @NonNull String overlayPath, @Nullable String overlayName, int policies, boolean enforce, int userId, @NonNull OverlayConstraint[] constraints)168     boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath,
169             @Nullable String overlayName, int policies, boolean enforce, int userId,
170             @NonNull OverlayConstraint[] constraints) throws Exception {
171         try (Connection c = connect()) {
172             final IIdmap2 idmap2 = c.getIdmap2();
173             if (idmap2 == null) {
174                 Slog.w(TAG, "idmap2d service is not ready for verifyIdmap(\"" + targetPath
175                         + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", "
176                         + enforce + ", " + userId + ")");
177                 return false;
178             }
179 
180             return idmap2.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
181                     policies, enforce, userId, constraints);
182         }
183     }
184 
idmapExists(String overlayPath, int userId)185     boolean idmapExists(String overlayPath, int userId) {
186         // The only way to verify an idmap is to read its state on disk.
187         final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
188         try (Connection c = connect()) {
189             final IIdmap2 idmap2 = c.getIdmap2();
190             if (idmap2 == null) {
191                 Slog.w(TAG, "idmap2d service is not ready for idmapExists(\"" + overlayPath
192                         + "\", " + userId + ")");
193                 return false;
194             }
195 
196             return new File(idmap2.getIdmapPath(overlayPath, userId)).isFile();
197         } catch (Exception e) {
198             Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
199             return false;
200         } finally {
201             StrictMode.setThreadPolicy(oldPolicy);
202         }
203     }
204 
createFabricatedOverlay(@onNull FabricatedOverlayInternal overlay)205     FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
206         try (Connection c = connect()) {
207             final IIdmap2 idmap2 = c.getIdmap2();
208             if (idmap2 == null) {
209                 Slog.w(TAG, "idmap2d service is not ready for createFabricatedOverlay()");
210                 return null;
211             }
212 
213             return idmap2.createFabricatedOverlay(overlay);
214         } catch (Exception e) {
215             Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e);
216             return null;
217         }
218     }
219 
deleteFabricatedOverlay(@onNull String path)220     boolean deleteFabricatedOverlay(@NonNull String path) {
221         try (Connection c = connect()) {
222             final IIdmap2 idmap2 = c.getIdmap2();
223             if (idmap2 == null) {
224                 Slog.w(TAG, "idmap2d service is not ready for deleteFabricatedOverlay(\"" + path
225                         + "\")");
226                 return false;
227             }
228 
229             return idmap2.deleteFabricatedOverlay(path);
230         } catch (Exception e) {
231             Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e);
232             return false;
233         }
234     }
235 
getFabricatedOverlayInfos()236     synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
237         final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>();
238         Connection c = null;
239         int iteratorId = -1;
240         try {
241             c = connect();
242             final IIdmap2 service = c.getIdmap2();
243             if (service == null) {
244                 Slog.w(TAG, "idmap2d service is not ready for getFabricatedOverlayInfos()");
245                 return Collections.emptyList();
246             }
247 
248             iteratorId = service.acquireFabricatedOverlayIterator();
249             List<FabricatedOverlayInfo> infos;
250             while (!(infos = service.nextFabricatedOverlayInfos(iteratorId)).isEmpty()) {
251                 allInfos.addAll(infos);
252             }
253             return allInfos;
254         } catch (Exception e) {
255             Slog.wtf(TAG, "failed to get all fabricated overlays", e);
256         } finally {
257             if (c != null) {
258                 try {
259                     if (c.getIdmap2() != null && iteratorId != -1) {
260                         c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId);
261                     }
262                 } catch (RemoteException e) {
263                     // ignore
264                 }
265                 c.close();
266             }
267         }
268         return allInfos;
269     }
270 
dumpIdmap(@onNull String overlayPath)271     String dumpIdmap(@NonNull String overlayPath) {
272         try (Connection c = connect()) {
273             final IIdmap2 service = c.getIdmap2();
274             if (service == null) {
275                 final String dumpText = "idmap2d service is not ready for dumpIdmap()";
276                 Slog.w(TAG, dumpText);
277                 return dumpText;
278             }
279             String dump = service.dumpIdmap(overlayPath);
280             return TextUtils.nullIfEmpty(dump);
281         } catch (Exception e) {
282             Slog.wtf(TAG, "failed to dump idmap", e);
283             return null;
284         }
285     }
286 
287     @Nullable
getIdmapServiceLocked()288     private IBinder getIdmapServiceLocked() throws TimeoutException, RemoteException {
289         try {
290             if (!SystemService.isRunning(IDMAP_DAEMON)) {
291                 SystemService.start(IDMAP_DAEMON);
292             }
293         } catch (RuntimeException e) {
294             Slog.wtf(TAG, "Failed to enable idmap2 daemon", e);
295             if (e.getMessage().contains("failed to set system property")) {
296                 return null;
297             }
298         }
299 
300         long uptimeMillis = SystemClock.uptimeMillis();
301         final long endUptimeMillis = uptimeMillis + SERVICE_CONNECT_UPTIME_TIMEOUT_MS;
302         long walltimeMillis = SystemClock.elapsedRealtime();
303         final long endWalltimeMillis = walltimeMillis + SERVICE_CONNECT_WALLTIME_TIMEOUT_MS;
304 
305         do {
306             final IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
307             if (binder != null) {
308                 binder.linkToDeath(
309                         () -> Slog.w(TAG,
310                                 TextUtils.formatSimple("service '%s' died", IDMAP_SERVICE)), 0);
311                 return binder;
312             }
313             SystemClock.sleep(SERVICE_CONNECT_INTERVAL_SLEEP_MS);
314         } while ((uptimeMillis = SystemClock.uptimeMillis()) <= endUptimeMillis
315                 && (walltimeMillis = SystemClock.elapsedRealtime()) <= endWalltimeMillis);
316 
317         throw new TimeoutException(
318                 TextUtils.formatSimple("Failed to connect to '%s' in %d/%d ms (spent %d/%d ms)",
319                         IDMAP_SERVICE, SERVICE_CONNECT_UPTIME_TIMEOUT_MS,
320                         SERVICE_CONNECT_WALLTIME_TIMEOUT_MS,
321                         uptimeMillis - endUptimeMillis + SERVICE_CONNECT_UPTIME_TIMEOUT_MS,
322                         walltimeMillis - endWalltimeMillis + SERVICE_CONNECT_WALLTIME_TIMEOUT_MS));
323     }
324 
stopIdmapServiceLocked()325     private static void stopIdmapServiceLocked() {
326         try {
327             if (SystemService.isRunning(IDMAP_DAEMON)) {
328                 SystemService.stop(IDMAP_DAEMON);
329             }
330         } catch (RuntimeException e) {
331             // If the idmap daemon cannot be disabled for some reason, it is okay
332             // since we already finished invoking idmap.
333             Slog.w(TAG, "Failed to disable idmap2 daemon", e);
334         }
335     }
336 
337     @NonNull
connect()338     private Connection connect() throws TimeoutException, RemoteException {
339         synchronized (mIdmapToken) {
340             FgThread.getHandler().removeCallbacksAndMessages(mIdmapToken);
341             if (mService != null) {
342                 // Not enough time has passed to stop the idmap service. Reuse the existing
343                 // interface.
344                 return new Connection(mService);
345             }
346 
347             IBinder binder = getIdmapServiceLocked();
348             if (binder == null) {
349                 return new Connection();
350             }
351 
352             mService = IIdmap2.Stub.asInterface(binder);
353             return new Connection(mService);
354         }
355     }
356 }
357