Android 14: Modify system partitions

I own a Fairphone 4 with the stock camera apk that I extracted from official firmware in 2023-11. In 2024-04, Fairphone released a major camera update and I wanted to get it on my LineageOS-based system.

Problem

A forum post describes that the .apk extracted from the firmware fails to install:

adb: failed to install FPCamera.apk:
 Failure [
  INSTALL_FAILED_MISSING_SHARED_LIBRARY: Reconciliation failed...:
  Reconcile failed:
  Package com.fp.camera requires unavailable native shared library libtctcameraalgo_jni.tct.so;
  failing!]

The new camera app (v7.00.04.0007.7.0)requires a shared library that is not available on my LineageOS firmware.

Extracting libtctcameraalgo_jni.tct.so

I extracted the current FP4-TP2L-factory.zip firmware image just as described in my previous blog post with unzip, simg2img and lpunpack. Then I mounted all partitions and looked into their file lists; it was system_ext that contained the library at

system_ext_a/lib64/libtctcameraalgo_jni.tct.so

(40kiB, md5 hash 3daaf896f43a33c7a883f57c7c4be3d5).

How not to modify the system partition

After running adb root I remounted the system_ext partition to be writable:

$ adb shell mount -o rw,remount /system_ext

This was how you did it in the pre-Android 10 days, and it does not work anymore because of dynamic Android partitions which are just as large as they need to be to contain the data:

$ adb push libtctcameraalgo_jni.tct.so /system_ext/lib64/
libtctcameraalgo_jni.tct.so: 1 file pushed, 0 skipped. 53.5 MB/s (40792 bytes in 0.001s)
adb: error: failed to copy 'libtctcameraalgo_jni.tct.so' to '/product/lib64/libtctcameraalgo_jni.tct.so': remote write failed: No space left on device

$ adb shell
FP4:/ # df -h /system_ext/
Filesystem      Size Used Avail Use% Mounted on
/dev/block/dm-3 392M 390M  1.2M 100% /system_ext

I opened a thread on reddit and was told by TimSchumi that I should use adb remount. That command creates overlayfs mounts that lets you modify the system partitions by modifying an "overlay" directory, which is then loaded onto the actual partition.

I also found the magic_overlayfs Magisk module, but deemed that using native tools would be easier. It had a nice short explanation for the problem, though:

On Android 10+, system partitions might no longer be able to remount as read-write. For devices using dynamic partitions, it is nearly impossible to modify the system partiton as there is no space left.

Remounting

$ adb remount
AVB verification is disabled, disabling verity state may have no effect
Using overlayfs for /system
Using overlayfs for /system_ext
Using overlayfs for /product
Using overlayfs for /vendor
Using overlayfs for /odm
[libfs_mgr] __mount(target=/system,flag=MS_PRIVATE)=-1: Invalid argument
Remounted /system as RW
Remounted /system_ext as RW
Remounted /product as RW
Remounted /vendor as RW
Remounted /odm as RW
Remounted /vendor/dsp as RW
Overlayfs enabled.
Remount succeeded
Now reboot your device for settings to take effect

Simply rebooting, creating the lib64 directory in the upper layer of the overlay and pushing the .so file into it did not work:

$ adb reboot
$ adb shell mkdir /mnt/scratch/overlay/system_ext/upper/lib64/
$ adb push libtctcameraalgo_jni.tct.so /mnt/scratch/overlay/system_ext/upper/lib64/
$ adb shell
FP4:/ $ ls /system_ext/lib64
ls: /system_ext/lib64: Operation not permitted
1|FP4:/ $ ls -lah /system_ext/
total 63
drwxr-xr-x  2 root root  4.0K 2009-01-01 01:00 lib
d?????????  ? ?    ?        ?                ? lib64
 
FP4:/ $ ls -lah /system_ext/lib64/
total 5.0K
drwxrwxrwx 1 root root 3.4K 2024-11-13 21:49 .
drwxr-xr-x 1 root root 3.4K 2024-11-13 21:46 ..
-rw-r--r--  1 root root 256K 2009-01-01 01:00 com.qualcomm.qti.dpm.api@1.0.so
-rw-r--r--  1 root root 141K 2009-01-01 01:00 lib-imsvideocodec.so
-rw-r--r--  1 root root 259K 2009-01-01 01:00 lib-imsvt.so
-rw-r--r--  1 root root  15K 2009-01-01 01:00 lib-imsvtextutils.so
-rw-r--r--  1 root root  32K 2009-01-01 01:00 lib-imsvtutils.so
[...]
-????????? ? ?    ?       ?                ? libtctcameraalgo_jni.tct.so

After modifying the upper layer directory, I had to reboot the phone every time to make Android see the changes properly. The reboots fixed the permission problems.

Library registration

When libtctcameraalgo_jni.tct.so was at the right place, installation still failed with

Package com.fp.camera requires unavailable native shared library libtctcameraalgo_jni.tct.so

I then read that libraries need to be registered - pm list libraries did not list it.

In order to add library to vendor partition, you need to create text file which lists all added libraries. Since Android N (7.0) or later, apps are only allowed to access libraries on a specific whitelist of NDK libraries.

We can create counterpart file for vendor and create it in following path: /vendor/etc/public.libraries.txt.

I moved the library from system_ext/lib64 to vendor/lib64/ because that one already had a etc/public.libraries.txt that I could modify:

$ adb shell
1|FP4:/mnt/scratch/overlay/vendor/upper/etc # echo libtctcameraalgo_jni.tct.so >> public.libraries.txt

Then I rebooted, and all broke down. The phone was stuck at the "Fairphone" boot logo and would not advance any further.

Things I tried

Fastboot and recovery were available, but I did not find a way to disable the remount overlays from there.

The previously linked Implement dynamic partitions writes

For developers using eng or userdebug builds, adb remount is extremely useful for fast iteration. Dynamic partitions pose a problem for adb remount because there is no longer free space within each file system. To address this, devices can enable overlayfs. As long as there is free space within the super partition, adb remount automatically creates a temporary dynamic partition and uses overlayfs for writes. The temporary partition is named scratch, so don't use this name for other partitions.

I tried to delete the partition with fastboot, but that did not help:

$ fastboot delete-logical-partition scratch
Deleting 'scratch'                                 OKAY [  0.009s]
Finished. Total time: 0.009s

In recovery adb, I also tried to delete the scratch partition but did not even find it. lptools_new allows to list dynamic partitions:

FP4:/ # lptools_new_arm64  --get-info
Slot: 0
Suffix: _b
Path to super: /dev/block/by-name/super
Group: qti_dynamic_partitions_b
Arguments: --get-info
 
GroupInSuper->qti_dynamic_partitions_b Usage->5422981120 TotalSpace->6441402368
NamePartInGroup->odm_b Size->1589248
NamePartInGroup->product_b Size->617897984
NamePartInGroup->system_b Size->971485184
NamePartInGroup->system_ext_b Size->416059392
NamePartInGroup->vendor_b Size->699158528
 
FP4:/ # lptools_new_arm64  --get-info --group cow
Slot: 0
Suffix: _b
Path to super: /dev/block/by-name/super
Group: cow
Arguments: --get-info
 
GroupInSuper->cow Usage->5422981120 TotalSpace->6441402368
NamePartInGroup->odm_b-cow Size->1601536
NamePartInGroup->product_b-cow Size->620318720
NamePartInGroup->system_b-cow Size->975286272
NamePartInGroup->system_ext_b-cow Size->417689600
NamePartInGroup->vendor_b-cow Size->701894656

Listing the partitions with fastboot is not directly possible, but partitions can be inferred from the variables:

$ fastboot getvar all
(bootloader) partition-size:super:0x180000000
[...]
(bootloader) partition-type:product_b:raw
[...]

No scratch there, though.

Bitter end

After 3 days I decided to give up and reinstalled the whole operating system. The SeedVault backup automatically reinstalled most applications, but unfortunately some of the applications I use do not support backing up their data:

Conversations did support backups, though.

Written by Christian Weiske.

Comments? Please send an e-mail.