Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request / RFC] 配合 FontManagerService 将字体文件安装到 data 分区 #46

Open
dantmnf opened this issue Sep 28, 2023 · 13 comments

Comments

@dantmnf
Copy link

dantmnf commented Sep 28, 2023

背景

/proc/self/maps 中会显示通过 mmap 映射的文件名和对应的设备号,通过 VFS 机制(Magisk 的 bind mount 或 KernelSU 的 overlayfs)修改的字体文件会显示 data 分区的设备号,从而触发部分应用的风控策略。

TB320FC:/ # grep ' /system/' /proc/$(pidof com.android.systemui)/maps
...
707b39d000-707b39e000 r--p 00000000 fe:09 3813                           /system/lib64/libasyncio.so
707b39e000-707b39f000 r-xp 00001000 fe:09 3813                           /system/lib64/libasyncio.so
707b39f000-707b3a0000 r--p 00002000 fe:09 3813                           /system/lib64/libasyncio.so
707b3d2000-707b3e2000 r--p 00000000 fe:09 4097                           /system/lib64/libmediadrmmetrics_full.so
707b3e2000-707b3f4000 r-xp 00010000 fe:09 4097                           /system/lib64/libmediadrmmetrics_full.so
707b3f4000-707b3f6000 r--p 00022000 fe:09 4097                           /system/lib64/libmediadrmmetrics_full.so
707b3f6000-707b3f7000 rw-p 00023000 fe:09 4097                           /system/lib64/libmediadrmmetrics_full.so
707f05e000-708096d000 r--p 00000000 fe:0f 89819                          /system/fonts/NotoSerifCJK-Regular.ttc
708096d000-7081c00000 r--p 00000000 fe:0f 49003                          /system/fonts/NotoSansCJK-Regular.ttc
7081c00000-7085231000 r--p 00000000 fe:0f 89876                          /system/fonts/NotoSerifCJK-VF.otf.ttc
7085231000-708715d000 r--p 00000000 fe:0f 88918                          /system/fonts/NotoSansCJK-VF.otf.ttc
708715d000-70873a1000 r--p 00000000 fe:09 2730                           /system/fonts/Roboto-Regular.ttf
708aa58000-708aab3000 r--p 00000000 fe:09 3940                           /system/lib64/libcrypto.so
708aab3000-708ab2a000 r-xp 0005b000 fe:09 3940                           /system/lib64/libcrypto.so
708ab2a000-708ab7b000 --xp 000d2000 fe:09 3940                           /system/lib64/libcrypto.so
708ab7b000-708ab80000 r-xp 00123000 fe:09 3940                           /system/lib64/libcrypto.so
708ab80000-708ab90000 r--p 00128000 fe:09 3940                           /system/lib64/libcrypto.so
708ab90000-708ab91000 rw-p 00137000 fe:09 3940                           /system/lib64/libcrypto.so
...

要消除此类痕迹,需要 VFS 路径与对应设备号匹配(即路径显示 /data/xxxxxx),或将对应地址替换为 memfd 映射(Shamiko)。

PoC

Android 12 提供了通过 OTA 单独更新字体文件的方法:https://source.android.com/docs/core/fonts/custom-font-fallback

字体文件受 fs-verity 以及签名验证保护(Android 12-13 为 fs-verity 签名验证,Android 14 为用户态签名验证),FontManagerService 启动时会删除 /data/fonts 下验证失败的文件。

  • Android 12-13 需要禁用 fs-verity 签名验证:echo 0 > /proc/sys/fs/verity/require_signatures
  • Android 14 需要使用 RRO 向 config_fontManagerServiceCerts 追加自签名证书

以下步骤是以 Android 13 为例。

# # 安装字体
# echo 0 > /proc/sys/fs/verity/require_signatures
# touch /data/local/tmp/dummy
# # 由于禁用了签名验证,可以使用空文件作为签名文件
# # 此处两个路径都需要 system_server 进程可读
# cmd font update /system/fonts/NotoSansCJK-VF.otf.ttc /data/local/tmp/dummy
Success
#
# # 修改模块
# echo "echo 0 > /proc/sys/fs/verity/require_signatures" >> /data/adb/modules/notocjk/post-fs-data.sh
# rm /data/adb/modules/notocjk/system/fonts/NotoSansCJK-VF.otf.ttc
#
# # 重启验证
# reboot
# grep fonts /proc/$(pidof com.android.systemui)/maps
72cf22b000-72d1157000 r--p 00000000 fe:0f 97458                          /data/fonts/files/~~xbiOPHgakY7ty1i3IeKxog==/NotoSansCJKjp-Thin.otc
735a8aa000-735c1b9000 r--p 00000000 fe:0f 95862                          /system/fonts/NotoSerifCJK-Regular.ttc
735c1b9000-735d44c000 r--p 00000000 fe:0f 95828                          /system/fonts/NotoSansCJK-Regular.ttc
735d44c000-7360a7d000 r--p 00000000 fe:0f 95836                          /system/fonts/NotoSerifCJK-VF.otf.ttc
7360a7d000-7360cc1000 r--p 00000000 fe:09 2730                           /system/fonts/Roboto-Regular.ttf
7696ce8000-7696d33000 r--p 00000000 fe:09 2731                           /system/fonts/RobotoStatic-Regular.ttf

其他信息

  • FontManagerService 只能更新字体文件以及 named family,fallback family 依然需要通过修改 fonts.xml 实现
  • fonts.xml 中需要引用正确的 PostScript name
  • 需要更高的 fontRevision 才能覆盖 /system/fonts 下相同 PostScript name 的字体
    • PostScript name 和 fontRevision 相同时,会优先使用 /system/fonts 中的字体文件
    • 或许可以 patch 一下 fontRevision?
  • 用户可以通过 cmd font clear 主动删除所有热更新字体
@WordlessEcho
Copy link
Collaborator

WordlessEcho commented Sep 30, 2023

我好像看到了很多可能性,比如用FontManagerService直接修改fonts.xml
至于你说的无法更新fallback family和需要正确的postScriptName这两点我还没看出来

# cmd font help

Font service (font) commands
help
    Print this help text.

dump [family name]
    Dump all font files in the specified family name.
    Dump current system font configuration if no family name was specified.

update [font file path] [signature file path]
    Update installed font files with new font file.

update-family [family definition XML path]
    Update font families with the new definitions.

install-debug-cert [cert file path]
    Install debug certificate file. This command can be used only on userdebug
    or eng device with root user.

clear
    Remove all installed font files and reset to the initial state.

restart
    Restart FontManagerService emulating device reboot.
    WARNING: this is not a safe operation. Other processes may misbehave if
    they are using fonts updated by FontManagerService.
    This command exists merely for testing.

status
    Prints status of current system font configuration.
# cmd font dump sans-serif | egrep 'Hans|Hant|Jpan|Kore|CJK'

Family: langTag = zh-Hans-CN
  FontStyle { weight=400, slant=0}, path = /system/fonts/NotoSansCJK-Regular.ttc, index = 2
Family: langTag = zh-Hant-TW,zh-Bopo-TW
  FontStyle { weight=400, slant=0}, path = /system/fonts/NotoSansCJK-Regular.ttc, index = 3
Family: langTag = ja-Jpan-JP
  FontStyle { weight=400, slant=0}, path = /system/fonts/NotoSansCJK-Regular.ttc
Family: langTag = ko-Kore-KR
  FontStyle { weight=400, slant=0}, path = /system/fonts/NotoSansCJK-Regular.ttc, index = 1
# cmd font dump serif | egrep 'Hans|Hant|Jpan|Kore|CJK'

Family: langTag = zh-Hans-CN
  FontStyle { weight=400, slant=0}, path = /system/fonts/NotoSerifCJK-Regular.ttc, index = 2
Family: langTag = zh-Hant-TW,zh-Bopo-TW
  FontStyle { weight=400, slant=0}, path = /system/fonts/NotoSerifCJK-Regular.ttc, index = 3
Family: langTag = ja-Jpan-JP
  FontStyle { weight=400, slant=0}, path = /system/fonts/NotoSerifCJK-Regular.ttc
Family: langTag = ko-Kore-KR
  FontStyle { weight=400, slant=0}, path = /system/fonts/NotoSerifCJK-Regular.ttc, index = 1

@WordlessEcho
Copy link
Collaborator

WordlessEcho commented Sep 30, 2023

update-family的具体用法

/**
* Parses XML representing {@link android.graphics.fonts.FontFamilyUpdateRequest}.
*
* <p>The format is like:
* <pre>{@code
*   <fontFamilyUpdateRequest>
*       <family name="family-name">
*           <font name="postScriptName"/>
*       </family>
*   </fontFamilyUpdateRequest>
* }</pre>
*/

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java;l=449;drc=e9b53c20c8ab211d13a483a037021de310577ab2

/**
* Read a {@link Family} instance from &lt;family&gt; element in XML
*
* For the XML format, see {@link Font} class comment.
*
* @param parser an XML parser that points &lt;family&gt; element.
* @return an {@link Family} instance
*/

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/graphics/fonts/FontUpdateRequest.java;l=276;drc=b14b5b8d12bf7f7a6dbf25cb275e247440450fce

/**
* Font object used for update.
*
* Here is an example of Family/Font XML.
* <family name="my-sans">
*   <font name="MySans" weight="400" slant="0" axis="'wght' 400 'ital' 0" index="0" />
*   <font name="MySans" weight="400" slant="0" axis="'wght' 400 'ital' 1" index="0" />
*   <font name="MySans" weight="400" slant="0" axis="'wght' 700 'ital' 0" index="0" />
*   <font name="MySans" weight="400" slant="0" axis="'wght' 700 'ital' 1" index="0" />
* </family>
*
* @see Font#readFromXml(XmlPullParser)
* @see Font#writeToXml(TypedXmlSerializer, Font)
* @see Family#readFromXml(XmlPullParser)
* @see Family#writeFamilyToXml(TypedXmlSerializer, Family)
*/

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/graphics/fonts/FontUpdateRequest.java;l=71;drc=b14b5b8d12bf7f7a6dbf25cb275e247440450fce

@WordlessEcho
Copy link
Collaborator

WordlessEcho commented Sep 30, 2023

看来读取postScriptName靠的是TTC index为0时,OpenType name table中的数据

fontFileName = mParser.buildFontFileName(tempNewFontFile);

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java;l=365;drc=b14b5b8d12bf7f7a6dbf25cb275e247440450fce

UpdatableFontDir(File filesDir, FontFileParser parser, FsverityUtil fsverityUtil,
        File configFile) {
    this(filesDir, parser, fsverityUtil, configFile,
            System::currentTimeMillis,
            (map) -> SystemFonts.getSystemFontConfig(map, 0, 0)
    );
}

// For unit testing
UpdatableFontDir(File filesDir, FontFileParser parser, FsverityUtil fsverityUtil,
        File configFile, Supplier<Long> currentTimeSupplier,
        Function<Map<String, File>, FontConfig> configSupplier) {
    mFilesDir = filesDir;
    mParser = parser;
    mFsverityUtil = fsverityUtil;
    mConfigFile = new AtomicFile(configFile);
    mCurrentTimeSupplier = currentTimeSupplier;
    mConfigSupplier = configSupplier;
}

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java;l=144;drc=b14b5b8d12bf7f7a6dbf25cb275e247440450fce

@Nullable
private UpdatableFontDir createUpdatableFontDir() {
    // Never read updatable font files in safe mode.
    if (mIsSafeMode) return null;
    // If apk verity is supported, fs-verity should be available.
    if (!VerityUtils.isFsVeritySupported()) return null;

    String[] certs = mContext.getResources().getStringArray(
            R.array.config_fontManagerServiceCerts);

    if (mDebugCertFilePath != null && (Build.IS_USERDEBUG || Build.IS_ENG)) {
        String[] tmp = new String[certs.length + 1];
        System.arraycopy(certs, 0, tmp, 0, certs.length);
        tmp[certs.length] = mDebugCertFilePath;
        certs = tmp;
    }

    return new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser(),
            new FsverityUtilImpl(certs), new File(CONFIG_XML_FILE));
}

// ...

private void initialize() {
    synchronized (mUpdatableFontDirLock) {
        mUpdatableFontDir = createUpdatableFontDir();
        if (mUpdatableFontDir == null) {
            setSerializedFontMap(serializeSystemServerFontMap());
            return;
        }
        mUpdatableFontDir.loadFontFileMap();
        updateSerializedFontMap();
    }
}

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/graphics/fonts/FontManagerService.java;l=263;drc=b14b5b8d12bf7f7a6dbf25cb275e247440450fce

@Override
public String getPostScriptName(File file) throws IOException {
    ByteBuffer buffer = mmap(file);
    try {
        return FontFileUtil.getPostScriptName(buffer, 0);
    } finally {
        unmap(buffer);
    }
}

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/graphics/fonts/OtfFontFileParser.java;l=40;drc=b14b5b8d12bf7f7a6dbf25cb275e247440450fce

/**
* Analyze name OpenType table and return PostScript name.
*
* IllegalArgumentException will be thrown for invalid font data.
* null will be returned if not found or the PostScript name is invalid.
*
* @param buffer a buffer of OpenType font
* @param index a font index
* @return a post script name or null if it is invalid or not found.
*/

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/graphics/java/android/graphics/fonts/FontFileUtil.java;l=164;drc=b14b5b8d12bf7f7a6dbf25cb275e247440450fce

@WordlessEcho
Copy link
Collaborator

我知道为啥说fallback改不了了(((

// We should keep the first font family (config.getFontFamilies().get(0)) because it's used
// as a fallback font. See SystemFonts.java.

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java;l=583;drc=b14b5b8d12bf7f7a6dbf25cb275e247440450fce

@WordlessEcho
Copy link
Collaborator

fontRevision是版本号吧?

<head>
  <!-- Most of this table will be recalculated by the compiler -->
  <tableVersion value="1.0"/>
  <fontRevision value="2.002"/>
  <!-- ... -->
</head>

@WordlessEcho
Copy link
Collaborator

# cmd font dump sans-serif | grep Droid

  FontStyle { weight=400, slant=0}, path = /system/fonts/DroidSansFallback.ttf
  FontStyle { weight=400, slant=0}, path = /system/fonts/DroidSansFallbackFull.ttf

@dantmnf
Copy link
Author

dantmnf commented Sep 30, 2023

fontRevision是版本号吧?

<head>
  <!-- Most of this table will be recalculated by the compiler -->
  <tableVersion value="1.0"/>
  <fontRevision value="2.002"/>
  <!-- ... -->
</head>

https://learn.microsoft.com/en-us/typography/opentype/spec/head

是一个定点数,而且整个文件有 checksum。

可能要写点 java 然后用 app_process 跑,顺便把现在那堆正则 xml 换掉(

@WordlessEcho
Copy link
Collaborator

好了,我研究完了,我现在的猜测是你想不再通过替换或新增任何文件的方式,修改字体?

目前我们替换和新增的文件有:

  • /system/etc/fonts.xml
  • /system_ext/etc/fonts.xml(一加氧OS/氢OS)
  • /system/fonts/DroidSansFallback.ttf
  • /system/fonts/DroidSansFallbackFull.ttf
  • /system/fonts/NotoSansCJK-Regular.ttc
  • /system/fonts/NotoSansCJK-VF.otf.ttc
  • /system/fonts/NotoSerifCJK-Regular.ttc
  • /system/fonts/NotoSerifCJK-VF.otf.ttc

如果想不修改任何文件,那是不可能的。至少fonts.xml要修改。然后字体如果你不想放到/system里,可以在fonts.xml里指定换个位置放

@WordlessEcho
Copy link
Collaborator

fontRevision是版本号吧?

<head>
  <!-- Most of this table will be recalculated by the compiler -->
  <tableVersion value="1.0"/>
  <fontRevision value="2.002"/>
  <!-- ... -->
</head>

https://learn.microsoft.com/en-us/typography/opentype/spec/head

是一个定点数,而且整个文件有 checksum。

可能要写点 java 然后用 app_process 跑,顺便把现在那堆正则 xml 换掉(

这个2.002的fontRevision是我用fonttool跑出来的,我很确定他是字体版本号

@WordlessEcho
Copy link
Collaborator

如果你想隐藏这个模块的话,是不是可以用Zygisk来做这个模块?可惜我完全不会Zygisk

@WordlessEcho
Copy link
Collaborator

WordlessEcho commented Sep 30, 2023

我重新思考了一下,你只是希望在/proc/self/maps里让文件的路径指向/data
我想的是用户可以装一个旧版的notocjk模块进去,然后写一个模块或脚本执行cmd font update XDDDDD

@dantmnf
Copy link
Author

dantmnf commented Sep 30, 2023

我重新思考了一下,你只是希望在/proc/self/maps里让文件的路径指向/data

对,主要是要在 /data 里找个应用能访问的地方


不过我又想了一下,应用还是可以在 /system 下面随便找一堆文件 stat 拿设备号,感觉意义不是太大(

真要藏模块可能要上增量更新的同款 dm-snapshot 了

@WordlessEcho WordlessEcho added the wontfix This will not be worked on label Oct 1, 2023
@WordlessEcho WordlessEcho removed the wontfix This will not be worked on label Jan 23, 2025
@WordlessEcho WordlessEcho reopened this Jan 23, 2025
@WordlessEcho
Copy link
Collaborator

Starting in Android 15, variable fonts are rendered at runtime with better efficiency and granularity. With this update, vendors must add new variable font configurations to font_fallback.xml instead of fonts.xml, as fonts.xml is being deprecated. See Support for variable fonts for more information.

Implement custom fonts | Android Open Source Project

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants