Loading graphics/java/android/graphics/fonts/SystemFonts.java +4 −2 Original line number Diff line number Diff line Loading @@ -50,9 +50,11 @@ public final class SystemFonts { private static final String DEFAULT_FAMILY = "sans-serif"; private static final String FONTS_XML = "/system/etc/fonts.xml"; private static final String SYSTEM_FONT_DIR = "/system/fonts/"; /** @hide */ public static final String SYSTEM_FONT_DIR = "/system/fonts/"; private static final String OEM_XML = "/product/etc/fonts_customization.xml"; private static final String OEM_FONT_DIR = "/product/fonts/"; /** @hide */ public static final String OEM_FONT_DIR = "/product/fonts/"; private SystemFonts() {} // Do not instansiate. Loading services/core/java/com/android/server/graphics/fonts/FontManagerService.java +53 −9 Original line number Diff line number Diff line Loading @@ -31,6 +31,8 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.security.FileIntegrityService; import com.android.server.security.VerityUtils; import java.io.File; import java.io.FileDescriptor; Loading @@ -39,6 +41,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.NioUtils; import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.Map; /** A service for managing system fonts. */ Loading Loading @@ -78,7 +81,17 @@ public final class FontManagerService { private static class OtfFontFileParser implements UpdatableFontDir.FontFileParser { @Override public long getVersion(File file) throws IOException { public String getPostScriptName(File file) throws IOException { ByteBuffer buffer = mmap(file); try { return FontFileUtil.getPostScriptName(buffer, 0); } finally { NioUtils.freeDirectBuffer(buffer); } } @Override public long getRevision(File file) throws IOException { ByteBuffer buffer = mmap(file); try { return FontFileUtil.getRevision(buffer, 0); Loading @@ -95,18 +108,48 @@ public final class FontManagerService { } } private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil { @Override public boolean hasFsverity(String filePath) { return VerityUtils.hasFsverity(filePath); } @Override public void setUpFsverity(String filePath, byte[] pkcs7Signature) throws IOException { VerityUtils.setUpFsverity(filePath, pkcs7Signature); } @Override public boolean rename(File src, File dest) { // rename system call preserves fs-verity bit. return src.renameTo(dest); } } @Nullable private final UpdatableFontDir mUpdatableFontDir; @GuardedBy("FontManagerService.this") @Nullable SystemFontSettings mCurrentFontSettings = null; @Nullable private SystemFontSettings mCurrentFontSettings = null; private FontManagerService() { mUpdatableFontDir = ENABLE_FONT_UPDATES ? new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser()) : null; mUpdatableFontDir = createUpdatableFontDir(); } @NonNull private SystemFontSettings getCurrentFontSettings() { @Nullable private static UpdatableFontDir createUpdatableFontDir() { if (!ENABLE_FONT_UPDATES) return null; // If apk verity is supported, fs-verity should be available. if (!FileIntegrityService.isApkVeritySupported()) return null; return new UpdatableFontDir(new File(FONT_FILES_DIR), Arrays.asList(new File(SystemFonts.SYSTEM_FONT_DIR), new File(SystemFonts.OEM_FONT_DIR)), new OtfFontFileParser(), new FsverityUtilImpl()); } @NonNull private SystemFontSettings getCurrentFontSettings() { synchronized (FontManagerService.this) { if (mCurrentFontSettings == null) { mCurrentFontSettings = SystemFontSettings.create(mUpdatableFontDir); Loading @@ -115,13 +158,14 @@ public final class FontManagerService { } } private boolean installFontFile(String name, FileDescriptor fd) { // TODO(b/173619554): Expose as API. private boolean installFontFile(FileDescriptor fd, byte[] pkcs7Signature) { if (mUpdatableFontDir == null) return false; synchronized (FontManagerService.this) { try { mUpdatableFontDir.installFontFile(name, fd); mUpdatableFontDir.installFontFile(fd, pkcs7Signature); } catch (IOException e) { Slog.w(TAG, "Failed to install font file: " + name, e); Slog.w(TAG, "Failed to install font file"); return false; } // Create updated font map in the next getSerializedSystemFontMap() call. Loading Loading @@ -194,5 +238,5 @@ public final class FontManagerService { } return null; } }; } } services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +213 −42 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.graphics.fonts; import android.annotation.Nullable; import android.os.FileUtils; import android.util.Base64; import android.util.Slog; Loading @@ -28,71 +29,158 @@ import java.io.FileOutputStream; import java.io.IOException; import java.security.SecureRandom; import java.util.HashMap; import java.util.List; import java.util.Map; final class UpdatableFontDir { private static final String TAG = "UpdatableFontDir"; private static final String RANDOM_DIR_PREFIX = "~~"; // TODO: Support .otf private static final String ALLOWED_EXTENSION = ".ttf"; /** Interface to mock font file access in tests. */ interface FontFileParser { long getVersion(File file) throws IOException; String getPostScriptName(File file) throws IOException; long getRevision(File file) throws IOException; } /** Interface to mock fs-verity in tests. */ interface FsverityUtil { boolean hasFsverity(String path); void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException; boolean rename(File src, File dest); } /** Data class to hold font file path and version. */ static final class FontFileInfo { final File mFile; final long mVersion; /** Data class to hold font file path and revision. */ private static final class FontFileInfo { private final File mFile; private final long mRevision; FontFileInfo(File file, long version) { FontFileInfo(File file, long revision) { mFile = file; mVersion = version; mRevision = revision; } public File getFile() { return mFile; } /** Returns the unique randomized font dir containing this font file. */ public File getRandomizedFontDir() { return mFile.getParentFile(); } public long getRevision() { return mRevision; } } /** * Root directory for storing updated font files. Each font file is stored in a unique random * dir. The font file path would be {@code mFilesDir/~~{randomStr}/{fontFileName}}. * Root directory for storing updated font files. Each font file is stored in a unique * randomized dir. The font file path would be {@code mFilesDir/~~{randomStr}/{fontFileName}}. */ private final File mFilesDir; private final List<File> mPreinstalledFontDirs; private final FontFileParser mParser; private final FsverityUtil mFsverityUtil; /** * A mutable map containing mapping from font file name (e.g. "NotoColorEmoji.ttf") to {@link * FontFileInfo}. All files in this map are validated, and have higher revision numbers than * corresponding font files in {@link #mPreinstalledFontDirs}. */ @GuardedBy("UpdatableFontDir.this") private final Map<String, FontFileInfo> mFontFileInfoMap = new HashMap<>(); UpdatableFontDir(File filesDir, FontFileParser parser) { UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser, FsverityUtil fsverityUtil) { mFilesDir = filesDir; mPreinstalledFontDirs = preinstalledFontDirs; mParser = parser; mFsverityUtil = fsverityUtil; loadFontFileMap(); } private void loadFontFileMap() { // TODO: SIGBUS crash protection synchronized (UpdatableFontDir.this) { boolean success = false; mFontFileInfoMap.clear(); try { File[] dirs = mFilesDir.listFiles(); if (dirs == null) return; for (File dir : dirs) { if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) continue; if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) return; File[] files = dir.listFiles(); if (files == null || files.length != 1) continue; addFileToMapLocked(files[0], true); if (files == null || files.length != 1) return; FontFileInfo fontFileInfo = validateFontFile(files[0]); if (fontFileInfo == null) { Slog.w(TAG, "Broken file is found. Clearing files."); return; } addFileToMapLocked(fontFileInfo, true /* deleteOldFile */); } success = true; } finally { // Delete all files just in case if we find a problematic file. if (!success) { mFontFileInfoMap.clear(); FileUtils.deleteContents(mFilesDir); } } } } void installFontFile(String name, FileDescriptor fd) throws IOException { // TODO: Validate name. /** * Installs a new font file, or updates an existing font file. * * <p>The new font will be immediately available for new Zygote-forked processes through * {@link #getFontFileMap()}. Old font files will be kept until next system server reboot, * because existing Zygote-forked processes have paths to old font files. * * @param fd A file descriptor to the font file. * @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file. */ void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws IOException { synchronized (UpdatableFontDir.this) { // TODO: proper error handling File newDir = getRandomDir(mFilesDir); if (!newDir.mkdir()) { // TODO: Define and return an error code for API throw new IOException("Failed to create a new dir"); } File newFontFile = new File(newDir, name); try (FileOutputStream out = new FileOutputStream(newFontFile)) { boolean success = false; try { File tempNewFontFile = new File(newDir, "font.ttf"); try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) { FileUtils.copy(fd, out.getFD()); } addFileToMapLocked(newFontFile, false); // Do not parse font file before setting up fs-verity. // setUpFsverity throws IOException if failed. mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(), pkcs7Signature); String postScriptName = mParser.getPostScriptName(tempNewFontFile); File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION); if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) { // TODO: Define and return an error code for API throw new IOException("Failed to rename"); } FontFileInfo fontFileInfo = validateFontFile(newFontFile); if (fontFileInfo == null) { // TODO: Define and return an error code for API throw new IllegalArgumentException("Invalid file"); } if (!addFileToMapLocked(fontFileInfo, false)) { // TODO: Define and return an error code for API throw new IllegalArgumentException("Version downgrade"); } success = true; } finally { if (!success) { FileUtils.deleteContentsAndDir(newDir); } } } } Loading @@ -114,27 +202,110 @@ final class UpdatableFontDir { return dir; } private void addFileToMapLocked(File file, boolean deleteOldFile) { final long version; /** * Add the given {@link FontFileInfo} to {@link #mFontFileInfoMap} if its font revision is * higher than the currently used font file (either in {@link #mFontFileInfoMap} or {@link * #mPreinstalledFontDirs}). */ private boolean addFileToMapLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) { String name = fontFileInfo.getFile().getName(); FontFileInfo existingInfo = mFontFileInfoMap.get(name); final boolean shouldAddToMap; if (existingInfo == null) { // We got a new updatable font. We need to check if it's newer than preinstalled fonts. // Note that getPreinstalledFontRevision() returns -1 if there is no preinstalled font // with 'name'. shouldAddToMap = getPreinstalledFontRevision(name) < fontFileInfo.getRevision(); } else { shouldAddToMap = existingInfo.getRevision() < fontFileInfo.getRevision(); } if (shouldAddToMap) { if (deleteOldFile && existingInfo != null) { FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir()); } mFontFileInfoMap.put(name, fontFileInfo); return true; } else { if (deleteOldFile) { FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir()); } return false; } } private long getPreinstalledFontRevision(String name) { long maxRevision = -1; for (File dir : mPreinstalledFontDirs) { File preinstalledFontFile = new File(dir, name); if (!preinstalledFontFile.exists()) continue; long revision = getFontRevision(preinstalledFontFile); if (revision == -1) { Slog.w(TAG, "Invalid preinstalled font file"); continue; } if (revision > maxRevision) { maxRevision = revision; } } return maxRevision; } /** * Checks the fs-verity protection status of the given font file, validates the file name, and * returns a {@link FontFileInfo} on success. This method does not check if the font revision * is higher than the currently used font. */ @Nullable private FontFileInfo validateFontFile(File file) { if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) { Slog.w(TAG, "Font validation failed. Fs-verity is not enabled: " + file); return null; } if (!validateFontFileName(file)) { Slog.w(TAG, "Font validation failed. Could not validate font file name: " + file); return null; } long revision = getFontRevision(file); if (revision == -1) { Slog.w(TAG, "Font validation failed. Could not read font revision: " + file); return null; } return new FontFileInfo(file, revision); } /** * Returns true if the font file's file name matches with the PostScript name metadata in the * font file. * * <p>We check the font file names because apps use file name to look up fonts. * <p>Because PostScript name does not include extension, the extension is appended for * comparison. For example, if the file name is "NotoColorEmoji.ttf", the PostScript name should * be "NotoColorEmoji". */ private boolean validateFontFileName(File file) { String fileName = file.getName(); String postScriptName = getPostScriptName(file); return (postScriptName + ALLOWED_EXTENSION).equals(fileName); } /** Returns the PostScript name of the given font file, or null. */ @Nullable private String getPostScriptName(File file) { try { version = mParser.getVersion(file); return mParser.getPostScriptName(file); } catch (IOException e) { Slog.e(TAG, "Failed to read font file", e); return; return null; } if (version == -1) { Slog.e(TAG, "Invalid font file"); return; } FontFileInfo info = mFontFileInfoMap.get(file.getName()); if (info == null) { // TODO: check version of font in /system/fonts and /product/fonts mFontFileInfoMap.put(file.getName(), new FontFileInfo(file, version)); } else if (info.mVersion < version) { if (deleteOldFile) { FileUtils.deleteContentsAndDir(info.mFile.getParentFile()); } mFontFileInfoMap.put(file.getName(), new FontFileInfo(file, version)); /** Returns the non-negative font revision of the given font file, or -1. */ private long getFontRevision(File file) { try { return mParser.getRevision(file); } catch (IOException e) { Slog.e(TAG, "Failed to read font file", e); return -1; } } Loading @@ -142,7 +313,7 @@ final class UpdatableFontDir { Map<String, File> map = new HashMap<>(); synchronized (UpdatableFontDir.this) { for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) { map.put(entry.getKey(), entry.getValue().mFile); map.put(entry.getKey(), entry.getValue().getFile()); } } return map; Loading services/core/java/com/android/server/security/FileIntegrityService.java +6 −2 Original line number Diff line number Diff line Loading @@ -60,8 +60,7 @@ public class FileIntegrityService extends SystemService { private final IBinder mService = new IFileIntegrityService.Stub() { @Override public boolean isApkVeritySupported() { return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; return FileIntegrityService.isApkVeritySupported(); } @Override Loading Loading @@ -111,6 +110,11 @@ public class FileIntegrityService extends SystemService { } }; public static boolean isApkVeritySupported() { return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; } public FileIntegrityService(final Context context) { super(context); try { Loading services/core/java/com/android/server/security/VerityUtils.java +6 −1 Original line number Diff line number Diff line Loading @@ -73,7 +73,12 @@ abstract public class VerityUtils { if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) { throw new SecurityException("Signature file is unexpectedly large: " + signaturePath); } byte[] pkcs7Signature = Files.readAllBytes(Paths.get(signaturePath)); setUpFsverity(filePath, Files.readAllBytes(Paths.get(signaturePath))); } /** Enables fs-verity for the file with a PKCS#7 detached signature bytes. */ public static void setUpFsverity(@NonNull String filePath, @NonNull byte[] pkcs7Signature) throws IOException { // This will fail if the public key is not already in .fs-verity kernel keyring. int errno = enableFsverityNative(filePath, pkcs7Signature); if (errno != 0) { Loading Loading
graphics/java/android/graphics/fonts/SystemFonts.java +4 −2 Original line number Diff line number Diff line Loading @@ -50,9 +50,11 @@ public final class SystemFonts { private static final String DEFAULT_FAMILY = "sans-serif"; private static final String FONTS_XML = "/system/etc/fonts.xml"; private static final String SYSTEM_FONT_DIR = "/system/fonts/"; /** @hide */ public static final String SYSTEM_FONT_DIR = "/system/fonts/"; private static final String OEM_XML = "/product/etc/fonts_customization.xml"; private static final String OEM_FONT_DIR = "/product/fonts/"; /** @hide */ public static final String OEM_FONT_DIR = "/product/fonts/"; private SystemFonts() {} // Do not instansiate. Loading
services/core/java/com/android/server/graphics/fonts/FontManagerService.java +53 −9 Original line number Diff line number Diff line Loading @@ -31,6 +31,8 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.security.FileIntegrityService; import com.android.server.security.VerityUtils; import java.io.File; import java.io.FileDescriptor; Loading @@ -39,6 +41,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.NioUtils; import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.Map; /** A service for managing system fonts. */ Loading Loading @@ -78,7 +81,17 @@ public final class FontManagerService { private static class OtfFontFileParser implements UpdatableFontDir.FontFileParser { @Override public long getVersion(File file) throws IOException { public String getPostScriptName(File file) throws IOException { ByteBuffer buffer = mmap(file); try { return FontFileUtil.getPostScriptName(buffer, 0); } finally { NioUtils.freeDirectBuffer(buffer); } } @Override public long getRevision(File file) throws IOException { ByteBuffer buffer = mmap(file); try { return FontFileUtil.getRevision(buffer, 0); Loading @@ -95,18 +108,48 @@ public final class FontManagerService { } } private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil { @Override public boolean hasFsverity(String filePath) { return VerityUtils.hasFsverity(filePath); } @Override public void setUpFsverity(String filePath, byte[] pkcs7Signature) throws IOException { VerityUtils.setUpFsverity(filePath, pkcs7Signature); } @Override public boolean rename(File src, File dest) { // rename system call preserves fs-verity bit. return src.renameTo(dest); } } @Nullable private final UpdatableFontDir mUpdatableFontDir; @GuardedBy("FontManagerService.this") @Nullable SystemFontSettings mCurrentFontSettings = null; @Nullable private SystemFontSettings mCurrentFontSettings = null; private FontManagerService() { mUpdatableFontDir = ENABLE_FONT_UPDATES ? new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser()) : null; mUpdatableFontDir = createUpdatableFontDir(); } @NonNull private SystemFontSettings getCurrentFontSettings() { @Nullable private static UpdatableFontDir createUpdatableFontDir() { if (!ENABLE_FONT_UPDATES) return null; // If apk verity is supported, fs-verity should be available. if (!FileIntegrityService.isApkVeritySupported()) return null; return new UpdatableFontDir(new File(FONT_FILES_DIR), Arrays.asList(new File(SystemFonts.SYSTEM_FONT_DIR), new File(SystemFonts.OEM_FONT_DIR)), new OtfFontFileParser(), new FsverityUtilImpl()); } @NonNull private SystemFontSettings getCurrentFontSettings() { synchronized (FontManagerService.this) { if (mCurrentFontSettings == null) { mCurrentFontSettings = SystemFontSettings.create(mUpdatableFontDir); Loading @@ -115,13 +158,14 @@ public final class FontManagerService { } } private boolean installFontFile(String name, FileDescriptor fd) { // TODO(b/173619554): Expose as API. private boolean installFontFile(FileDescriptor fd, byte[] pkcs7Signature) { if (mUpdatableFontDir == null) return false; synchronized (FontManagerService.this) { try { mUpdatableFontDir.installFontFile(name, fd); mUpdatableFontDir.installFontFile(fd, pkcs7Signature); } catch (IOException e) { Slog.w(TAG, "Failed to install font file: " + name, e); Slog.w(TAG, "Failed to install font file"); return false; } // Create updated font map in the next getSerializedSystemFontMap() call. Loading Loading @@ -194,5 +238,5 @@ public final class FontManagerService { } return null; } }; } }
services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +213 −42 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.graphics.fonts; import android.annotation.Nullable; import android.os.FileUtils; import android.util.Base64; import android.util.Slog; Loading @@ -28,71 +29,158 @@ import java.io.FileOutputStream; import java.io.IOException; import java.security.SecureRandom; import java.util.HashMap; import java.util.List; import java.util.Map; final class UpdatableFontDir { private static final String TAG = "UpdatableFontDir"; private static final String RANDOM_DIR_PREFIX = "~~"; // TODO: Support .otf private static final String ALLOWED_EXTENSION = ".ttf"; /** Interface to mock font file access in tests. */ interface FontFileParser { long getVersion(File file) throws IOException; String getPostScriptName(File file) throws IOException; long getRevision(File file) throws IOException; } /** Interface to mock fs-verity in tests. */ interface FsverityUtil { boolean hasFsverity(String path); void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException; boolean rename(File src, File dest); } /** Data class to hold font file path and version. */ static final class FontFileInfo { final File mFile; final long mVersion; /** Data class to hold font file path and revision. */ private static final class FontFileInfo { private final File mFile; private final long mRevision; FontFileInfo(File file, long version) { FontFileInfo(File file, long revision) { mFile = file; mVersion = version; mRevision = revision; } public File getFile() { return mFile; } /** Returns the unique randomized font dir containing this font file. */ public File getRandomizedFontDir() { return mFile.getParentFile(); } public long getRevision() { return mRevision; } } /** * Root directory for storing updated font files. Each font file is stored in a unique random * dir. The font file path would be {@code mFilesDir/~~{randomStr}/{fontFileName}}. * Root directory for storing updated font files. Each font file is stored in a unique * randomized dir. The font file path would be {@code mFilesDir/~~{randomStr}/{fontFileName}}. */ private final File mFilesDir; private final List<File> mPreinstalledFontDirs; private final FontFileParser mParser; private final FsverityUtil mFsverityUtil; /** * A mutable map containing mapping from font file name (e.g. "NotoColorEmoji.ttf") to {@link * FontFileInfo}. All files in this map are validated, and have higher revision numbers than * corresponding font files in {@link #mPreinstalledFontDirs}. */ @GuardedBy("UpdatableFontDir.this") private final Map<String, FontFileInfo> mFontFileInfoMap = new HashMap<>(); UpdatableFontDir(File filesDir, FontFileParser parser) { UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser, FsverityUtil fsverityUtil) { mFilesDir = filesDir; mPreinstalledFontDirs = preinstalledFontDirs; mParser = parser; mFsverityUtil = fsverityUtil; loadFontFileMap(); } private void loadFontFileMap() { // TODO: SIGBUS crash protection synchronized (UpdatableFontDir.this) { boolean success = false; mFontFileInfoMap.clear(); try { File[] dirs = mFilesDir.listFiles(); if (dirs == null) return; for (File dir : dirs) { if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) continue; if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) return; File[] files = dir.listFiles(); if (files == null || files.length != 1) continue; addFileToMapLocked(files[0], true); if (files == null || files.length != 1) return; FontFileInfo fontFileInfo = validateFontFile(files[0]); if (fontFileInfo == null) { Slog.w(TAG, "Broken file is found. Clearing files."); return; } addFileToMapLocked(fontFileInfo, true /* deleteOldFile */); } success = true; } finally { // Delete all files just in case if we find a problematic file. if (!success) { mFontFileInfoMap.clear(); FileUtils.deleteContents(mFilesDir); } } } } void installFontFile(String name, FileDescriptor fd) throws IOException { // TODO: Validate name. /** * Installs a new font file, or updates an existing font file. * * <p>The new font will be immediately available for new Zygote-forked processes through * {@link #getFontFileMap()}. Old font files will be kept until next system server reboot, * because existing Zygote-forked processes have paths to old font files. * * @param fd A file descriptor to the font file. * @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file. */ void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws IOException { synchronized (UpdatableFontDir.this) { // TODO: proper error handling File newDir = getRandomDir(mFilesDir); if (!newDir.mkdir()) { // TODO: Define and return an error code for API throw new IOException("Failed to create a new dir"); } File newFontFile = new File(newDir, name); try (FileOutputStream out = new FileOutputStream(newFontFile)) { boolean success = false; try { File tempNewFontFile = new File(newDir, "font.ttf"); try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) { FileUtils.copy(fd, out.getFD()); } addFileToMapLocked(newFontFile, false); // Do not parse font file before setting up fs-verity. // setUpFsverity throws IOException if failed. mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(), pkcs7Signature); String postScriptName = mParser.getPostScriptName(tempNewFontFile); File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION); if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) { // TODO: Define and return an error code for API throw new IOException("Failed to rename"); } FontFileInfo fontFileInfo = validateFontFile(newFontFile); if (fontFileInfo == null) { // TODO: Define and return an error code for API throw new IllegalArgumentException("Invalid file"); } if (!addFileToMapLocked(fontFileInfo, false)) { // TODO: Define and return an error code for API throw new IllegalArgumentException("Version downgrade"); } success = true; } finally { if (!success) { FileUtils.deleteContentsAndDir(newDir); } } } } Loading @@ -114,27 +202,110 @@ final class UpdatableFontDir { return dir; } private void addFileToMapLocked(File file, boolean deleteOldFile) { final long version; /** * Add the given {@link FontFileInfo} to {@link #mFontFileInfoMap} if its font revision is * higher than the currently used font file (either in {@link #mFontFileInfoMap} or {@link * #mPreinstalledFontDirs}). */ private boolean addFileToMapLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) { String name = fontFileInfo.getFile().getName(); FontFileInfo existingInfo = mFontFileInfoMap.get(name); final boolean shouldAddToMap; if (existingInfo == null) { // We got a new updatable font. We need to check if it's newer than preinstalled fonts. // Note that getPreinstalledFontRevision() returns -1 if there is no preinstalled font // with 'name'. shouldAddToMap = getPreinstalledFontRevision(name) < fontFileInfo.getRevision(); } else { shouldAddToMap = existingInfo.getRevision() < fontFileInfo.getRevision(); } if (shouldAddToMap) { if (deleteOldFile && existingInfo != null) { FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir()); } mFontFileInfoMap.put(name, fontFileInfo); return true; } else { if (deleteOldFile) { FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir()); } return false; } } private long getPreinstalledFontRevision(String name) { long maxRevision = -1; for (File dir : mPreinstalledFontDirs) { File preinstalledFontFile = new File(dir, name); if (!preinstalledFontFile.exists()) continue; long revision = getFontRevision(preinstalledFontFile); if (revision == -1) { Slog.w(TAG, "Invalid preinstalled font file"); continue; } if (revision > maxRevision) { maxRevision = revision; } } return maxRevision; } /** * Checks the fs-verity protection status of the given font file, validates the file name, and * returns a {@link FontFileInfo} on success. This method does not check if the font revision * is higher than the currently used font. */ @Nullable private FontFileInfo validateFontFile(File file) { if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) { Slog.w(TAG, "Font validation failed. Fs-verity is not enabled: " + file); return null; } if (!validateFontFileName(file)) { Slog.w(TAG, "Font validation failed. Could not validate font file name: " + file); return null; } long revision = getFontRevision(file); if (revision == -1) { Slog.w(TAG, "Font validation failed. Could not read font revision: " + file); return null; } return new FontFileInfo(file, revision); } /** * Returns true if the font file's file name matches with the PostScript name metadata in the * font file. * * <p>We check the font file names because apps use file name to look up fonts. * <p>Because PostScript name does not include extension, the extension is appended for * comparison. For example, if the file name is "NotoColorEmoji.ttf", the PostScript name should * be "NotoColorEmoji". */ private boolean validateFontFileName(File file) { String fileName = file.getName(); String postScriptName = getPostScriptName(file); return (postScriptName + ALLOWED_EXTENSION).equals(fileName); } /** Returns the PostScript name of the given font file, or null. */ @Nullable private String getPostScriptName(File file) { try { version = mParser.getVersion(file); return mParser.getPostScriptName(file); } catch (IOException e) { Slog.e(TAG, "Failed to read font file", e); return; return null; } if (version == -1) { Slog.e(TAG, "Invalid font file"); return; } FontFileInfo info = mFontFileInfoMap.get(file.getName()); if (info == null) { // TODO: check version of font in /system/fonts and /product/fonts mFontFileInfoMap.put(file.getName(), new FontFileInfo(file, version)); } else if (info.mVersion < version) { if (deleteOldFile) { FileUtils.deleteContentsAndDir(info.mFile.getParentFile()); } mFontFileInfoMap.put(file.getName(), new FontFileInfo(file, version)); /** Returns the non-negative font revision of the given font file, or -1. */ private long getFontRevision(File file) { try { return mParser.getRevision(file); } catch (IOException e) { Slog.e(TAG, "Failed to read font file", e); return -1; } } Loading @@ -142,7 +313,7 @@ final class UpdatableFontDir { Map<String, File> map = new HashMap<>(); synchronized (UpdatableFontDir.this) { for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) { map.put(entry.getKey(), entry.getValue().mFile); map.put(entry.getKey(), entry.getValue().getFile()); } } return map; Loading
services/core/java/com/android/server/security/FileIntegrityService.java +6 −2 Original line number Diff line number Diff line Loading @@ -60,8 +60,7 @@ public class FileIntegrityService extends SystemService { private final IBinder mService = new IFileIntegrityService.Stub() { @Override public boolean isApkVeritySupported() { return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; return FileIntegrityService.isApkVeritySupported(); } @Override Loading Loading @@ -111,6 +110,11 @@ public class FileIntegrityService extends SystemService { } }; public static boolean isApkVeritySupported() { return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; } public FileIntegrityService(final Context context) { super(context); try { Loading
services/core/java/com/android/server/security/VerityUtils.java +6 −1 Original line number Diff line number Diff line Loading @@ -73,7 +73,12 @@ abstract public class VerityUtils { if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) { throw new SecurityException("Signature file is unexpectedly large: " + signaturePath); } byte[] pkcs7Signature = Files.readAllBytes(Paths.get(signaturePath)); setUpFsverity(filePath, Files.readAllBytes(Paths.get(signaturePath))); } /** Enables fs-verity for the file with a PKCS#7 detached signature bytes. */ public static void setUpFsverity(@NonNull String filePath, @NonNull byte[] pkcs7Signature) throws IOException { // This will fail if the public key is not already in .fs-verity kernel keyring. int errno = enableFsverityNative(filePath, pkcs7Signature); if (errno != 0) { Loading