ARM向けChromiumのクロスビルドとUPXによるバイナリフットプリントの最適化

## はじめに

x86_64のUbuntu 24.04環境で aarch64 向けにChromiumをクロスビルドし、さらに[UPX (Ultimate Packer for Executables)](https://upx.github.io/) で圧縮した Chromium の実行ファイルをaarch64のUbuntu 24.04環境で正常動作することを確認した。

## まとめ

aarch64向けにクロスビルドした Chromium のバイナリサイズ (chromeファイルのサイズ) を次の表に示す

|処理 | Chromiumのバイナリサイズ (MiB)|
|:-|:-|
|リリース向け設定でクロスビルド | 357 |
|strip でシンボル削除 | 234 |
| upx で圧縮 | 93 |


## UPX (Ultimate Packer for Executables)

UPXとは、さまざまな形式の可実行ファイルを圧縮しファイルサイズを縮小するためのオープンソースのパッカーだ。バイナリファイルを圧縮することができ、圧縮後もそのまま実行可能である。実行ファイルだけでなく動的リンクされる共有ライブラリの圧縮にも対応している。

次のような特徴がある
- 高い圧縮率
    - ファイルサイズを大幅に小さくできる
- 高速な解凍
    - 圧縮された実行ファイルは実行時に自動でメモリ上に展開される。PC上であれば起動時の解凍にかかる時間は無視できるほど高速だと期待できる
    - 組み込み機器の場合にはファイルをメモリにコピーする速度と解凍にかかる時間とのトレードオフ
- クロスプラットフォーム対応
    - Windows、Linux、macOSなど、多くのオペレーティングシステム向けのバイナリをサポートする
- [License](https://github.com/upx/upx/blob/devel/LICENSE)は例外付きのGPL
    - 自己解凍機能のため upx の一部が圧縮したソフトウェアに埋め込まれる
    - ただし `SPECIAL EXCEPTION FOR COMPRESSED EXECUTABLES` という例外を認めているため、改造していない upx を使う限り、upx で圧縮したソフトウェアを配布しても GPL の影響を受けない


### UPX のセットアップ

UPX をUbuntu24.04 環境にインストールする

```bash
sudo apt update
sudo apt-get install upx
```

インストールした upx のバージョンを確認

```bash
$ upx --version
upx 4.2.2
UCL data compression library 1.03
zlib data compression library 1.3.0.1-motley
LZMA SDK version 4.43
doctest C++ testing framework version 2.4.11
Copyright (C) 1996-2024 Markus Franz Xaver Johannes Oberhumer
Copyright (C) 1996-2024 Laszlo Molnar
Copyright (C) 2000-2024 John F. Reiser
Copyright (C) 2002-2024 Jens Medoch
Copyright (C) 1995-2023 Jean-loup Gailly and Mark Adler
Copyright (C) 1999-2006 Igor Pavlov
Copyright (C) 2016-2023 Viktor Kirilov
UPX comes with ABSOLUTELY NO WARRANTY; for details type 'upx -L'.
```

### UPX の使用方法

UPX の使用方法と動作を確認する

#### ls コマンドを圧縮する

ls コマンドを適当なディレクトにコピーする

```bash
$ cp `which ls` .
```

コピーしたファイルサイズが 140KiB だと確認できる

```bash
$ du -h ./ls
140K	./ls
```

upx コマンドで ls を圧縮すると 64KiB (下のサイズの44.15%) まで圧縮される

```bash
$ upx ./ls
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar & John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    142312 ->     62824   44.15%   linux/amd64   ls                            

Packed 1 file.

$ du -sh ./ls
64K	./ls
```

圧縮した ls コマンドの正常動作を確認できる

```bash
$ ./ls -l
total 64
-rwxr-xr-x 1 raiko raiko 62824  9月 30 12:07 ls
```


## 動的リンクするソフトウェアを upx で圧縮

upx は実行ファイルと同様に動的リンクされる共有ライブラリも圧縮可能だ。共有ライブラリを圧縮した場合にも正常動作することを[サンプルプログラム](https://github.com/aRaikoFunakami/upx_sample.git)を使って確認する

### サンプルプログラム

- main.c : 動的リンクされた共有ライブラリのadd関数を呼び出す
- main_with_dlopen.c : 共有ライブラリを明示的に読み込んでadd関数を呼び出す
- add.c : add関数を提供する共有ライブラリになる

```c:main.c
// main.c
#include <stdio.h>
extern int add(int a, int b);

int main() {
    int a = 5;
    int b = 3;
    int result = add(a, b);

    printf("main.c: Result of %d + %d = %d\n", a, b, result);
    return 0;
}
```

```c:main_with_dlopen.c
// main_with_dlopen.c
#include <stdio.h>
#include <dlfcn.h>

int main() {
    
    // 共有ライブラリをロード
#ifdef __aarch64__
    void *handle = dlopen("./libadd_aarch64.so", RTLD_LAZY);
#else
    void *handle = dlopen("./libadd.so", RTLD_LAZY);
#endif
    if (!handle) {
        fprintf(stderr, "Failed to load library: %s\n", dlerror());
        return 1;
    }

    // 関数ポインタを取得
    int (*add)(int, int) = (int (*)(int, int)) dlsym(handle, "add");
    const char *error = dlerror();
    if (error != NULL) {
        fprintf(stderr, "Failed to load function: %s\n", error);
        dlclose(handle);
        return 1;
    }

    // 関数を呼び出し
    int a = 5;
    int b = 3;
    int result = add(a, b);
    printf("main_with_dlopen.c: Result of %d + %d = %d\n", a, b, result);

    // ライブラリを閉じる
    dlclose(handle);

    return 0;
}
```

```c:add.c
// add.c
int add(int a, int b) {
    return a + b;
}
```

### サンプルプログラムの動作確認

実行ファイル main, main_with_dlopen と、共有ライブラリ libadd.so を作成して実行結果を確認する

```bash
# ソースコードの取得
git clone https://github.com/aRaikoFunakami/upx_sample.git
cd upx_sample
# コンパイル
gcc -fPIC -shared -o libadd.so add.c
gcc -o main main.c -L. -ladd
gcc -o main_with_dlopen main_with_dlopen.c -ldl
# 実行
LD_LIBRARY_PATH=. ./main
LD_LIBRARY_PATH=. ./main_with_dlopen
```

### 期待出力

main と main_with_dlopen がそれぞれ正しく動作している

```bash
main.c: Result of 5 + 3 = 8
main_with_dlopen.c: Result of 5 + 3 = 8
```


### upx で圧縮

圧縮前のそれぞれのファイルサイズを確認しておく

```bash
$ ls -lh main main_with_dlopen libadd.so 
-rwxrwxr-x 1 raiko raiko 15K  9月 30 12:50 libadd.so
-rwxrwxr-x 1 raiko raiko 16K  9月 30 12:50 main
-rwxrwxr-x 1 raiko raiko 16K  9月 30 12:50 main_with_dlopen
```

upx コマンドで実行ファイルと共有ライブラリを圧縮する。通常 strip したファイルを upx で圧縮するであろうから、ここでも strip した後に upx で圧縮する。

```bash
$ strip main main_with_dlopen libadd.so 
$ upx main main_with_dlopen libadd.so 
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar & John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     14464 ->      5400   37.33%   linux/amd64   main                          
     14464 ->      5872   40.60%   linux/amd64   main_with_dlopen              
     14008 ->      8236   58.79%   linux/amd64   libadd.so                     
   --------------------   ------   -----------   -----------
     42936 ->     19508   45.44%                 [ 3 files ]

Packed 3 files.
```

strip した後に upx で圧縮した場合、main, main_with_dlopen, libadd.so それぞれのバイナリサイズが大きく削減できた

```bash
$ ls -lh main main_with_dlopen libadd.so 
-rwxrwxr-x 1 raiko raiko 8.1K  9月 30 12:43 libadd.so
-rwxrwxr-x 1 raiko raiko 5.3K  9月 30 12:43 main
-rwxrwxr-x 1 raiko raiko 5.8K  9月 30 12:43 main_with_dlopen
```

upx で圧縮したファイルが正常動作することを確認した

```bash
LD_LIBRARY_PATH=. ./main
LD_LIBRARY_PATH=. ./main_with_dlopen
main.c: Result of 5 + 3 = 8
main_with_dlopen.c: Result of 5 + 3 = 8
```

## クロスコンパイルへの対応状況

さきほどのサンプルプログラムを aarch64 向けにクロスコンパイルし、そのバイナリを upx で圧縮して実行を確認する

### upx の対応状況

現時点で aarm64 (arm64) を含めて一般的に利用されるプラットフォーム向けの形式に対応している。現時点では残念ながら[RISC-Vには未対応で開発中](https://github.com/search?q=repo%3Aupx%2Fupx+IMAGE_FILE_MACHINE_RISCV&type=code)のようだ。

`mipsel.r3000-ps1`のように初代PlayStationがサポートされていることも確認できる

```bash
$ upx --help
...
This version supports:
    amd64-darwin.dylib                   dylib/amd64
    amd64-darwin.macho                   macho/amd64
    amd64-linux.elf                      linux/amd64
    amd64-linux.kernel.vmlinux           vmlinux/amd64
    amd64-win64.pe                       win64/pe
    arm-darwin.macho                     macho/arm
    arm-linux.elf                        linux/arm
    arm-linux.kernel.vmlinux             vmlinux/arm
    arm-linux.kernel.vmlinuz             vmlinuz/arm
    arm-wince.pe                         wince/arm
    arm64-darwin.macho                   macho/arm64
    arm64-linux.elf                      linux/arm64
    armeb-linux.elf                      linux/armeb
    armeb-linux.kernel.vmlinux           vmlinux/armeb
    fat-darwin.macho                     macho/fat
    i086-dos16.com                       dos/com
    i086-dos16.exe                       dos/exe
    i086-dos16.sys                       dos/sys
    i386-bsd.elf.execve                  bsd.exec/i386
    i386-darwin.macho                    macho/i386
    i386-dos32.djgpp2.coff               djgpp2/coff
    i386-dos32.tmt.adam                  tmt/adam
    i386-dos32.watcom.le                 watcom/le
    i386-freebsd.elf                     freebsd/i386
    i386-linux.elf                       linux/i386
    i386-linux.elf.execve                linux.exec/i386
    i386-linux.elf.shell                 linux.sh/i386
    i386-linux.kernel.bvmlinuz           bvmlinuz/i386
    i386-linux.kernel.vmlinux            vmlinux/i386
    i386-linux.kernel.vmlinuz            vmlinuz/i386
    i386-netbsd.elf                      netbsd/i386
    i386-openbsd.elf                     openbsd/i386
    i386-win32.pe                        win32/pe
    m68k-atari.tos                       atari/tos
    mips-linux.elf                       linux/mips
    mipsel-linux.elf                     linux/mipsel
    mipsel.r3000-ps1                     ps1/exe
    powerpc-darwin.macho                 macho/ppc32
    powerpc-linux.elf                    linux/ppc32
    powerpc-linux.kernel.vmlinux         vmlinux/ppc32
    powerpc64-linux.elf                  linux/ppc64
    powerpc64le-linux.elf                linux/ppc64le
    powerpc64le-linux.kernel.vmlinux     vmlinux/ppc64le
```

### クロスコンパイラのインストール

aarch64 環境向けのビルドツールと実行確認用に QEMU を用意する

```bash
sudo apt install -y g++-aarch64-linux-gnu
sudo apt install -y qemu-system-aarch64
```

### クロスコンパイルしたプログラムの実行結果

クロスコンパイルしたファイルのファイルが aarch64 向けの実行ファイルもしくは共有ライブラリだと確認でき、またそれらが qemu 上で正常動作したことが確認できる。

```bash
aarch64-linux-gnu-gcc -fPIC -shared -o libadd_aarch64.so add.c
aarch64-linux-gnu-gcc -o main_aarch64 main.c -L. -ladd_aarch64
aarch64-linux-gnu-gcc -o main_with_dlopen_aarch64 main_with_dlopen.c -ldl
file main_aarch64 main_with_dlopen_aarch64 libadd_aarch64.so
LD_LIBRARY_PATH=. qemu-aarch64 -L /usr/aarch64-linux-gnu ./main_aarch64
LD_LIBRARY_PATH=. qemu-aarch64 -L /usr/aarch64-linux-gnu ./main_with_dlopen_aarch64
```


```bash:実行結果
main_aarch64:             ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=37c953d80fad00f01946b0e05ea320e7358daf19, for GNU/Linux 3.7.0, not stripped
main_with_dlopen_aarch64: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=441a7b9233ce75ecd06270698fa06ad39db30abc, for GNU/Linux 3.7.0, not stripped
libadd_aarch64.so:        ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=da4bcc02b42294218d7f60c0a01f2d5956bf5349, not stripped
main.c: Result of 5 + 3 = 8
main_with_dlopen.c: Result of 5 + 3 = 8
```

### upx で圧縮した実行結果を確認

upx で圧縮前のファイルサイズを確認しておく

```bash
ls -lh  main_aarch64 main_with_dlopen_aarch64 libadd_aarch64.so
-rwxrwxr-x 1 raiko raiko 68K  9月 30 13:01 libadd_aarch64.so
-rwxrwxr-x 1 raiko raiko 69K  9月 30 13:01 main_aarch64
-rwxrwxr-x 1 raiko raiko 69K  9月 30 13:01 main_with_dlopen_aarch64
```

クロスコンパイルして作成した実行ファイルと共有ライブラリを strip した後に upx で圧縮する。 main_aarch64 と main_with_dlopen_aarch64 は圧縮できたが、 libadd_aarch64.so の圧縮には失敗した。このエラーには複数の要因があるようだが、今回は圧縮率が悪いため圧縮する必要なしと判定されていた。

```bash
$ aarch64-linux-gnu-strip main_aarch64 main_with_dlopen_aarch64  libadd_aarch64.so
$ upx main_aarch64 main_with_dlopen_aarch64 libadd_aarch64.so
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar & John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     67488 ->      6700    9.93%   linux/arm64   main_aarch64                  
     67488 ->      7232   10.72%   linux/arm64   main_with_dlopen_aarch64      
note: use --android-shlib if appropriate
upx: libadd_aarch64.so: NotCompressibleException                               
   --------------------   ------   -----------   -----------
    134976 ->     13932   10.32%                 [ 2 files ]

Packed 3 files: 2 ok, 1 error.

$ ls -lh  main_aarch64 main_with_dlopen_aarch64 libadd_aarch64.so
-rwxrwxr-x 1 raiko raiko  66K  9月 30 13:07 libadd_aarch64.so
-rwxrwxr-x 1 raiko raiko 6.6K  9月 30 13:07 main_aarch64
-rwxrwxr-x 1 raiko raiko 7.1K  9月 30 13:07 main_with_dlopen_aarch64
```

圧縮した実行ファイルをQEMU上で実行し正常動作することを確認した。

```bash
LD_LIBRARY_PATH=. qemu-aarch64 -L /usr/aarch64-linux-gnu ./main_aarch64
LD_LIBRARY_PATH=. qemu-aarch64 -L /usr/aarch64-linux-gnu ./main_with_dlopen_aarch64
```
```bash:実行結果
main.c: Result of 5 + 3 = 8
main_with_dlopen.c: Result of 5 + 3 = 8 
```

### upx で libadd_aarch64.so の圧縮に失敗した原因

圧縮率が 6.25% 以下であったため、[最後の return false; でエラー判定](https://github.com/upx/upx/blob/44e4bd0b5454ff8aee1ff3376974dfe6014300d9/src/packer.cpp#L288)となっていた

```c:packer.cpp
bool Packer::checkDefaultCompressionRatio(unsigned u_len, unsigned c_len) const {
    assert((int) u_len > 0);
    assert((int) c_len > 0);
    if (c_len >= u_len)
        return false;
    unsigned gain = u_len - c_len;

    if (gain < 512) // need at least 512 bytes gain
        return false;
#if 1
    if (gain >= 4096) // ok if we have at least 4096 bytes gain
        return true;
#endif
    if (gain >= u_len / 16) // ok if we have at least 6.25% gain
        return true;
    return false;
}
```


## クロスビルドしたChromiumを圧縮

### aarch64向け Chromium のビルド

x86_64版と同等の作業で[Chromiumのソースコードを取得する](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/linux/build_instructions.md
)

```bash
# $HOMEディレクトリで作業した場合
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH="${HOME}/depot_tools:$PATH"
mkdir ~/chromium && cd ~/chromium
fetch --nohooks --nohistory chromium
```

次に、[aarch64版のクロスビルドを行う](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/linux/chromium_arm.md)

```bash
# chromium/src 以下で
cd src

# ビルドのセットアップ
./build/install-build-deps.sh
./build/linux/sysroot_scripts/install-sysroot.py --arch=arm64
gclient runhooks
# gn gen out/arm64 --args='target_os="linux" target_cpu="arm64"'

# リリース版のビルドセットアップ
gn gen out/aarch64 --args='target_os="linux" target_cpu="arm64" is_debug=false is_component_build=false symbol_level=0 enable_nacl=false blink_symbol_level=0 v8_symbol_level=0 is_official_build = true chrome_pgo_phase = 0'
autoninja -C out/aarch64 chrome

# クロスビルド開始
autoninja -C out/arm64 chrome
```

### クロスビルドした chromium のバイナリを確認

クロスビルドした chrome ファイルのバイナリサイズを確認すると 375MiB である。

```bash
＄ls -lSh out/aarch64/chrome out/aarch64/*.so
-rwxrwxr-x 1 raiko raiko 357M  9月 30 09:18 out/aarch64/chrome
-rwxrwxr-x 1 raiko raiko  24M  9月 29 17:11 out/aarch64/libvk_swiftshader.so
-rwxrwxr-x 1 raiko raiko  23M  9月 29 17:20 out/aarch64/libVkLayer_khronos_validation.so
-rwxrwxr-x 1 raiko raiko 8.9M  9月 29 17:13 out/aarch64/libGLESv2.so
-rwxrwxr-x 1 raiko raiko 595K  9月 29 17:12 out/aarch64/libVkICD_mock_icd.so
-rwxrwxr-x 1 raiko raiko 398K  9月 29 17:14 out/aarch64/libEGL.so
-rwxrwxr-x 1 raiko raiko  42K  9月 29 19:26 out/aarch64/libqt6_shim.so
-rwxrwxr-x 1 raiko raiko  38K  9月 29 19:26 out/aarch64/libqt5_shim.so

```

strip後は chrome ファイルのバイナリサイズは 234MiB になり

```bash
$ aarch64-linux-gnu-strip out/aarch64/chrome out/aarch64/*.so
$ ls -lSh out/aarch64/chrome out/aarch64/*.so
-rwxrwxr-x 1 raiko raiko 234M  9月 30 11:16 out/aarch64/chrome
-rwxrwxr-x 1 raiko raiko  17M  9月 30 11:16 out/aarch64/libvk_swiftshader.so
-rwxrwxr-x 1 raiko raiko  16M  9月 30 11:16 out/aarch64/libVkLayer_khronos_validation.so
-rwxrwxr-x 1 raiko raiko 6.2M  9月 30 11:16 out/aarch64/libGLESv2.so
-rwxrwxr-x 1 raiko raiko 356K  9月 30 11:16 out/aarch64/libVkICD_mock_icd.so
-rwxrwxr-x 1 raiko raiko 255K  9月 30 11:16 out/aarch64/libEGL.so
-rwxrwxr-x 1 raiko raiko  29K  9月 30 11:16 out/aarch64/libqt6_shim.so
-rwxrwxr-x 1 raiko raiko  27K  9月 30 11:16 out/aarch64/libqt5_shim.so
```

upxで圧縮すると 93MiB まで　chrome ファイルのバイナリサイズを削減できた。

```bash
upx out/aarch64/chrome out/aarch64/*.so
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar & John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
 244518080 ->  96672028   39.54%   linux/arm64   chrome                        
note: use --android-shlib if appropriate
    260400 ->    192292   73.84%   linux/arm64   libEGL.so                     
note: use --android-shlib if appropriate
   6489272 ->   3668652   56.53%   linux/arm64   libGLESv2.so                  
note: use --android-shlib if appropriate
    364008 ->    230364   63.29%   linux/arm64   libVkICD_mock_icd.so          
note: use --android-shlib if appropriate
  16675304 ->  10577884   63.43%   linux/arm64   libVkLayer_khronos_validation.so
note: use --android-shlib if appropriate
     26936 ->     24740   91.85%   linux/arm64   libqt5_shim.so                
note: use --android-shlib if appropriate
     29096 ->     26900   92.45%   linux/arm64   libqt6_shim.so                
note: use --android-shlib if appropriate
  16900528 ->  10540892   62.37%   linux/arm64   libvk_swiftshader.so          
   --------------------   ------   -----------   -----------
 285263624 -> 121933752   42.74%                 [ 8 files ]
```

圧縮後の chrome ファイルと全ての共有ライブラリの合計は116MiBであった

```bash
$ ls -l out/aarch64/chrome out/aarch64/*.so | awk '{sum+=$5} END {print sum/1024/1024 " MiB"}'
116.285 MiB
```


### 圧縮した Chromium の動作確認

Chromiumはとても重いプログラムなのでQEMUでCPUをエミュレートした場合にはパフォーマンス的に苦しいため、M3 MacBook上で [Tart](https://tart.run/) VM で動作させたaarch64版のUbuntu24.04で動作確認を行った。

```bash:aarch64版のUbuntu24.04
$ uname -a
Linux ubuntu 6.8.0-45-generic #45-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 30 12:26:41 UTC 2024 aarch64 aarch64 aarch64 GNU/Linux
```

aarch64版のUbuntu24.04にビルドしたライブラリとリソースをコピーする

```bash:aarch64版のUbuntu24.04
REMOTE_HOST=raiko@192.168.1.113 \
    scp -r $REMOTE_HOST:~/chromium/src/out/aarch64/chrome \
    $REMOTE_HOST:~/chromium/src/out/aarch64/resources \
    $REMOTE_HOST:~/chromium/src/out/aarch64/locales \
    $REMOTE_HOST:~/chromium/src/out/aarch64/icudtl.dat \
    $REMOTE_HOST:~/chromium/src/out/aarch64/chrome_crashpad_handler \
    $REMOTE_HOST:~/chromium/src/out/aarch64/v8_context_snapshot.bin \
    $REMOTE_HOST:~/chromium/src/out/aarch64/*.pak  \
    $REMOTE_HOST:~/chromium/src/out/aarch64/*.so .
```

Chromiumの起動する

```bash
./chrome --no-sandbox
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/e302cd4c-6d84-6772-c110-e7c3bfd742d5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/3a9b7ca6-57b2-7003-f53f-921c10571a7d.png)

YouTubeで動画が再生されることを確認した


## 蛇足
Ubuntu24.04などAppArmorが利用されている環境で`--no-sandbox`をつけずに実行したい場合は下記を参考に設定を行うことで`--no-sandbox`を付けずに実行可能となる。

https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md