1 /* 2 * Copyright (C) 2018 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; 18 19 import static android.os.SystemUpdateManager.KEY_STATUS; 20 import static android.os.SystemUpdateManager.STATUS_IDLE; 21 import static android.os.SystemUpdateManager.STATUS_UNKNOWN; 22 23 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; 24 import static org.xmlpull.v1.XmlPullParser.END_TAG; 25 import static org.xmlpull.v1.XmlPullParser.START_TAG; 26 27 import android.Manifest; 28 import android.annotation.Nullable; 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.os.Binder; 32 import android.os.Build; 33 import android.os.Bundle; 34 import android.os.Environment; 35 import android.os.ISystemUpdateManager; 36 import android.os.PersistableBundle; 37 import android.os.SystemUpdateManager; 38 import android.provider.Settings; 39 import android.util.AtomicFile; 40 import android.util.Slog; 41 import android.util.Xml; 42 43 import com.android.internal.util.FastXmlSerializer; 44 import com.android.internal.util.XmlUtils; 45 import com.android.modules.utils.TypedXmlPullParser; 46 import com.android.modules.utils.TypedXmlSerializer; 47 48 import org.xmlpull.v1.XmlPullParser; 49 import org.xmlpull.v1.XmlPullParserException; 50 import org.xmlpull.v1.XmlSerializer; 51 52 import java.io.File; 53 import java.io.FileInputStream; 54 import java.io.FileNotFoundException; 55 import java.io.FileOutputStream; 56 import java.io.IOException; 57 import java.nio.charset.StandardCharsets; 58 59 public class SystemUpdateManagerService extends ISystemUpdateManager.Stub { 60 61 private static final String TAG = "SystemUpdateManagerService"; 62 63 private static final int UID_UNKNOWN = -1; 64 65 private static final String INFO_FILE = "system-update-info.xml"; 66 private static final int INFO_FILE_VERSION = 0; 67 private static final String TAG_INFO = "info"; 68 private static final String KEY_VERSION = "version"; 69 private static final String KEY_UID = "uid"; 70 private static final String KEY_BOOT_COUNT = "boot-count"; 71 private static final String KEY_INFO_BUNDLE = "info-bundle"; 72 73 private final Context mContext; 74 private final AtomicFile mFile; 75 private final Object mLock = new Object(); 76 private int mLastUid = UID_UNKNOWN; 77 private int mLastStatus = STATUS_UNKNOWN; 78 SystemUpdateManagerService(Context context)79 public SystemUpdateManagerService(Context context) { 80 mContext = context; 81 mFile = new AtomicFile(new File(Environment.getDataSystemDirectory(), INFO_FILE)); 82 83 // Populate mLastUid and mLastStatus. 84 synchronized (mLock) { 85 loadSystemUpdateInfoLocked(); 86 } 87 } 88 89 @android.annotation.EnforcePermission(android.Manifest.permission.RECOVERY) 90 @Override updateSystemUpdateInfo(PersistableBundle infoBundle)91 public void updateSystemUpdateInfo(PersistableBundle infoBundle) { 92 updateSystemUpdateInfo_enforcePermission(); 93 94 int status = infoBundle.getInt(KEY_STATUS, STATUS_UNKNOWN); 95 if (status == STATUS_UNKNOWN) { 96 Slog.w(TAG, "Invalid status info. Ignored"); 97 return; 98 } 99 100 // There could be multiple updater apps running on a device. But only one at most should 101 // be active (i.e. with a pending update), with the rest reporting idle status. We will 102 // only accept the reported status if any of the following conditions holds: 103 // a) none has been reported before; 104 // b) the current on-file status was last reported by the same caller; 105 // c) an active update is being reported. 106 int uid = Binder.getCallingUid(); 107 if (mLastUid == UID_UNKNOWN || mLastUid == uid || status != STATUS_IDLE) { 108 synchronized (mLock) { 109 saveSystemUpdateInfoLocked(infoBundle, uid); 110 } 111 } else { 112 Slog.i(TAG, "Inactive updater reporting IDLE status. Ignored"); 113 } 114 } 115 116 @Override retrieveSystemUpdateInfo()117 public Bundle retrieveSystemUpdateInfo() { 118 if (mContext.checkCallingOrSelfPermission(Manifest.permission.READ_SYSTEM_UPDATE_INFO) 119 == PackageManager.PERMISSION_DENIED 120 && mContext.checkCallingOrSelfPermission(Manifest.permission.RECOVERY) 121 == PackageManager.PERMISSION_DENIED) { 122 throw new SecurityException("Can't read system update info. Requiring " 123 + "READ_SYSTEM_UPDATE_INFO or RECOVERY permission."); 124 } 125 126 synchronized (mLock) { 127 return loadSystemUpdateInfoLocked(); 128 } 129 } 130 131 // Reads and validates the info file. Returns the loaded info bundle on success; or a default 132 // info bundle with UNKNOWN status. loadSystemUpdateInfoLocked()133 private Bundle loadSystemUpdateInfoLocked() { 134 PersistableBundle loadedBundle = null; 135 try (FileInputStream fis = mFile.openRead()) { 136 TypedXmlPullParser parser = Xml.resolvePullParser(fis); 137 loadedBundle = readInfoFileLocked(parser); 138 } catch (FileNotFoundException e) { 139 Slog.i(TAG, "No existing info file " + mFile.getBaseFile()); 140 } catch (XmlPullParserException e) { 141 Slog.e(TAG, "Failed to parse the info file:", e); 142 } catch (IOException e) { 143 Slog.e(TAG, "Failed to read the info file:", e); 144 } 145 146 // Validate the loaded bundle. 147 if (loadedBundle == null) { 148 return removeInfoFileAndGetDefaultInfoBundleLocked(); 149 } 150 151 int version = loadedBundle.getInt(KEY_VERSION, -1); 152 if (version == -1) { 153 Slog.w(TAG, "Invalid info file (invalid version). Ignored"); 154 return removeInfoFileAndGetDefaultInfoBundleLocked(); 155 } 156 157 int lastUid = loadedBundle.getInt(KEY_UID, -1); 158 if (lastUid == -1) { 159 Slog.w(TAG, "Invalid info file (invalid UID). Ignored"); 160 return removeInfoFileAndGetDefaultInfoBundleLocked(); 161 } 162 163 int lastBootCount = loadedBundle.getInt(KEY_BOOT_COUNT, -1); 164 if (lastBootCount == -1 || lastBootCount != getBootCount()) { 165 Slog.w(TAG, "Outdated info file. Ignored"); 166 return removeInfoFileAndGetDefaultInfoBundleLocked(); 167 } 168 169 PersistableBundle infoBundle = loadedBundle.getPersistableBundle(KEY_INFO_BUNDLE); 170 if (infoBundle == null) { 171 Slog.w(TAG, "Invalid info file (missing info). Ignored"); 172 return removeInfoFileAndGetDefaultInfoBundleLocked(); 173 } 174 175 int lastStatus = infoBundle.getInt(KEY_STATUS, STATUS_UNKNOWN); 176 if (lastStatus == STATUS_UNKNOWN) { 177 Slog.w(TAG, "Invalid info file (invalid status). Ignored"); 178 return removeInfoFileAndGetDefaultInfoBundleLocked(); 179 } 180 181 // Everything looks good upon reaching this point. 182 mLastStatus = lastStatus; 183 mLastUid = lastUid; 184 return new Bundle(infoBundle); 185 } 186 saveSystemUpdateInfoLocked(PersistableBundle infoBundle, int uid)187 private void saveSystemUpdateInfoLocked(PersistableBundle infoBundle, int uid) { 188 // Wrap the incoming bundle with extra info (e.g. version, uid, boot count). We use nested 189 // PersistableBundle to avoid manually parsing XML attributes when loading the info back. 190 PersistableBundle outBundle = new PersistableBundle(); 191 outBundle.putPersistableBundle(KEY_INFO_BUNDLE, infoBundle); 192 outBundle.putInt(KEY_VERSION, INFO_FILE_VERSION); 193 outBundle.putInt(KEY_UID, uid); 194 outBundle.putInt(KEY_BOOT_COUNT, getBootCount()); 195 196 // Only update the info on success. 197 if (writeInfoFileLocked(outBundle)) { 198 mLastUid = uid; 199 mLastStatus = infoBundle.getInt(KEY_STATUS); 200 } 201 } 202 203 // Performs I/O work only, without validating the loaded info. 204 @Nullable readInfoFileLocked(TypedXmlPullParser parser)205 private PersistableBundle readInfoFileLocked(TypedXmlPullParser parser) 206 throws XmlPullParserException, IOException { 207 int type; 208 while ((type = parser.next()) != END_DOCUMENT) { 209 if (type == START_TAG && TAG_INFO.equals(parser.getName())) { 210 return PersistableBundle.restoreFromXml(parser); 211 } 212 } 213 return null; 214 } 215 writeInfoFileLocked(PersistableBundle outBundle)216 private boolean writeInfoFileLocked(PersistableBundle outBundle) { 217 FileOutputStream fos = null; 218 try { 219 fos = mFile.startWrite(); 220 221 TypedXmlSerializer out = Xml.resolveSerializer(fos); 222 out.startDocument(null, true); 223 224 out.startTag(null, TAG_INFO); 225 outBundle.saveToXml(out); 226 out.endTag(null, TAG_INFO); 227 228 out.endDocument(); 229 mFile.finishWrite(fos); 230 return true; 231 } catch (IOException | XmlPullParserException e) { 232 Slog.e(TAG, "Failed to save the info file:", e); 233 if (fos != null) { 234 mFile.failWrite(fos); 235 } 236 } 237 return false; 238 } 239 removeInfoFileAndGetDefaultInfoBundleLocked()240 private Bundle removeInfoFileAndGetDefaultInfoBundleLocked() { 241 if (mFile.exists()) { 242 Slog.i(TAG, "Removing info file"); 243 mFile.delete(); 244 } 245 246 mLastStatus = STATUS_UNKNOWN; 247 mLastUid = UID_UNKNOWN; 248 Bundle infoBundle = new Bundle(); 249 infoBundle.putInt(KEY_STATUS, STATUS_UNKNOWN); 250 return infoBundle; 251 } 252 getBootCount()253 private int getBootCount() { 254 return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0); 255 } 256 } 257