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