blob: 686ae2bbaedea5256b083ad451d987a249b03b8a [file] [log] [blame]
David Anderson28dea682019-02-20 13:37:51 -08001/*
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
17package com.android.server.locksettings;
18
19import android.os.SystemProperties;
20import android.util.Slog;
21
22import com.android.internal.annotations.VisibleForTesting;
23
24import java.io.File;
25import java.io.FileInputStream;
26import java.io.FileOutputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.OutputStream;
30import java.nio.file.Paths;
31import java.util.Collections;
32import java.util.HashMap;
33import java.util.HashSet;
34import java.util.Map;
35import java.util.Properties;
36import java.util.Set;
37
38/**
39 * A class that maintains a mapping of which password slots are used by alternate OS images when
40 * dual-booting a device. Currently, slots can either be owned by the host OS or a live GSI.
41 * This mapping is stored in /metadata/password_slots/slot_map using Java Properties.
42 *
43 * If a /metadata partition does not exist, GSIs are not supported, and PasswordSlotManager will
44 * simply not persist the slot mapping.
45 */
46public class PasswordSlotManager {
47 private static final String TAG = "PasswordSlotManager";
48
49 private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
50 private static final String SLOT_MAP_DIR = "/metadata/password_slots";
51
52 // This maps each used password slot to the OS image that created it. Password slots are
53 // integer keys/indices into secure storage. The OS image is recorded as a string. The factory
54 // image is "host" and GSIs are "gsi<N>" where N >= 1.
55 private final Map<Integer, String> mSlotMap;
56
57 public PasswordSlotManager() {
58 mSlotMap = loadSlotMap();
59 }
60
61 @VisibleForTesting
62 protected String getSlotMapDir() {
63 return SLOT_MAP_DIR;
64 }
65
66 @VisibleForTesting
67 protected int getGsiImageNumber() {
68 return SystemProperties.getInt(GSI_RUNNING_PROP, 0);
69 }
70
71 /**
72 * Notify the manager of which slots are definitively in use by the current OS image.
73 *
74 * @throws RuntimeException
75 */
76 public void refreshActiveSlots(Set<Integer> activeSlots) throws RuntimeException {
77 // Update which slots are owned by the current image.
78 final HashSet<Integer> slotsToDelete = new HashSet<Integer>();
79 for (Map.Entry<Integer, String> entry : mSlotMap.entrySet()) {
80 // Delete possibly stale entries for the current image.
81 if (entry.getValue().equals(getMode())) {
82 slotsToDelete.add(entry.getKey());
83 }
84 }
85 for (Integer slot : slotsToDelete) {
86 mSlotMap.remove(slot);
87 }
88
89 // Add slots for the current image.
90 for (Integer slot : activeSlots) {
91 mSlotMap.put(slot, getMode());
92 }
93
94 saveSlotMap();
95 }
96
97 /**
98 * Mark the given slot as in use by the current OS image.
99 *
100 * @throws RuntimeException
101 */
102 public void markSlotInUse(int slot) throws RuntimeException {
103 if (mSlotMap.containsKey(slot) && !mSlotMap.get(slot).equals(getMode())) {
104 throw new RuntimeException("password slot " + slot + " is not available");
105 }
106 mSlotMap.put(slot, getMode());
107 saveSlotMap();
108 }
109
110 /**
111 * Mark the given slot as no longer in use by the current OS image.
112 *
113 * @throws RuntimeException
114 */
115 public void markSlotDeleted(int slot) throws RuntimeException {
116 if (mSlotMap.containsKey(slot) && mSlotMap.get(slot) != getMode()) {
117 throw new RuntimeException("password slot " + slot + " cannot be deleted");
118 }
119 mSlotMap.remove(slot);
120 saveSlotMap();
121 }
122
123 /**
124 * Return the set of slots used across all OS images.
125 *
126 * @return Integer set of all used slots.
127 */
128 public Set<Integer> getUsedSlots() {
129 return Collections.unmodifiableSet(mSlotMap.keySet());
130 }
131
132 private File getSlotMapFile() {
133 return Paths.get(getSlotMapDir(), "slot_map").toFile();
134 }
135
136 private String getMode() {
137 int gsiIndex = getGsiImageNumber();
138 if (gsiIndex > 0) {
139 return "gsi" + gsiIndex;
140 }
141 return "host";
142 }
143
144 @VisibleForTesting
145 protected Map<Integer, String> loadSlotMap(InputStream stream) throws IOException {
146 final HashMap<Integer, String> map = new HashMap<Integer, String>();
147 final Properties props = new Properties();
148 props.load(stream);
149 for (String slotString : props.stringPropertyNames()) {
150 final int slot = Integer.parseInt(slotString);
151 final String owner = props.getProperty(slotString);
152 map.put(slot, owner);
153 }
154 return map;
155 }
156
157 private Map<Integer, String> loadSlotMap() {
158 // It's okay if the file doesn't exist.
159 final File file = getSlotMapFile();
160 if (file.exists()) {
161 try (FileInputStream stream = new FileInputStream(file)) {
162 return loadSlotMap(stream);
163 } catch (Exception e) {
164 Slog.e(TAG, "Could not load slot map file", e);
165 }
166 }
167 return new HashMap<Integer, String>();
168 }
169
170 @VisibleForTesting
171 protected void saveSlotMap(OutputStream stream) throws IOException {
172 final Properties props = new Properties();
173 for (Map.Entry<Integer, String> entry : mSlotMap.entrySet()) {
174 props.setProperty(entry.getKey().toString(), entry.getValue());
175 }
176 props.store(stream, "");
177 }
178
179 private void saveSlotMap() {
180 if (!getSlotMapFile().getParentFile().exists()) {
181 Slog.w(TAG, "Not saving slot map, " + getSlotMapDir() + " does not exist");
182 return;
183 }
184
185 try (FileOutputStream fos = new FileOutputStream(getSlotMapFile())) {
186 saveSlotMap(fos);
187 } catch (IOException e) {
188 Slog.e(TAG, "failed to save password slot map", e);
189 }
190 }
191}