# Environment
官方文件有提供 step-by-step 的教學,但會踩到滿多坑的,記錄一下讓團隊內其他人未來可以快速建立環境。
因為會用到 GUI 介面,建議用 VirtualBox 或 VMWare 直接開一台 Virtual Machine 來設定。若有時間再嘗試用 Docker。
以下分別紀錄在 Local 環境和 Production 環境執行 ClusterFuzz 設定的步驟。
## Local Setup
Local 環境建置,我進行了兩個版本的測試,分別用 Docker 和 Virtual machine 建立。
根據官方文件,基本環境設置需要有 Google Cloud SDK, Python3.7, Go (未指明版本), Google Cloud SDK 在官方的 `install_deps.bash` 中就會安裝。
### Docker
一開始為了方便快速建立環境,選擇在 docker 中安裝需要的套件並對 host mapping 會用到的 Port (9000),以便在 host 的瀏覽器頁面可以進到 Clusterfuzz 服務的頁面。Dockerfile 內容如下:
```dockerfile
# Use ubuntu:20.04 to fulfill the requirements of GLIBC version (higher than 2.28)
# ubuntu:20.04 -> GLIBC version 2.31
FROM ubuntu:20.04@sha256:3246518d9735254519e1b2ff35f95686e4a5011c90c85344c1f38df7bae9dd37
# Install
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && \
DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential wget curl git gnupg zlib1g-dev libffi-dev libsqlite3-dev libssl-dev lsb-core openssl apt-transport-https ca-certificates vim tmux sudo
# Install Python 3.7.7
RUN cd /tmp/ && \
wget https://www.python.org/ftp/python/3.7.7/Python-3.7.7.tgz && \
tar xzf Python-3.7.7.tgz && \
cd Python-3.7.7 && \
./configure --prefix=/opt/python/3.7.7/ --enable-loadable-sqlite-extensions --with-computed-gotos --with-system-ffi && \
make -j "$(nproc)" && \
make altinstall && \
rm /tmp/Python-3.7.7.tgz && \
ln -s /opt/python/3.7.7/bin/python3.7 /usr/local/bin/python3
# Install clusterfuzz dependencies
RUN cd /root/ && \
git clone https://github.com/google/clusterfuzz.git && \
cd clusterfuzz && \
git pull && \
git checkout tags/v2.6.0 && \
./local/install_deps.bash
```
基於此 Dockerfile ,執行 `docker build -t clusterfuzz .` 建立 image。
這邊特別注意因為 Clusterfuzz 只支援在 x86_64 架構下執行,如果是 M1 晶片的 Mac 使用者需要特別指定 Ubuntu 為 amd64 版本的,否則預設會抓 arm64 的 Ubuntu,到後面安裝 dependencies 時會出問題。
建立好 image 之後,執行 `docker run -it —name <name> -p 9000:9000 -p 9001:9001 -p 9002:9002 /bin/bash` 進入容器環境。
進入容器後可以用 `pwd` 檢查一下當前的路徑,應該會在 `/root/clusterfuzz` 底下。開始執行 python script 之前,Clusterfuzz 要求必須要在 python 的虛擬環境下執行 script,而 Clusterfuzz 官方是使用 `pipenv` 這套工具,因此必須先執行:
```bash
$ export SHELL=/bin/bash
$ python3.8 -m pipenv shell
```
應該會看到類似這樣的訊息:
```bash
Launching subshell in virtual environment...
. /root/.local/share/virtualenvs/clusterfuzz--qC05KhX/bin/activate
```
就代表成功進到 Python 的虛擬環境了。接下來可以開始執行 clusterfuzz 的 script。開始前建議先開 `tmux` ,因為接下來有些 script (clusterfuzz 的 server) 會持續執行,要執行其他 script 需要多開終端機視窗。
```bash
# 第一次起 server 或要重新設定時必須加 --bootstrap
$ python butler.py run_server --bootstrap
# 後續起 server 加 --skip-install-deps
$ python butler.py run_server --skip-install-deps
```
等待 script 執行一陣子,看到下列訊息就代表服務有架設成功:
```bash
Running: gunicorn -b :9000 main:app (cwd='src/appengine')
| [2023-08-16 16:49:04 +0800] [12246] [INFO] Starting gunicorn 20.1.0
| [2023-08-16 16:49:04 +0800] [12246] [INFO] Listening at: http://0.0.0.0:9000 (12246)
| [2023-08-16 16:49:04 +0800] [12246] [INFO] Using worker: sync
| [2023-08-16 16:49:04 +0800] [12270] [INFO] Booting worker with pid: 12270
```
這時在本機端的瀏覽器打 `http://localhost:9000` 就可以看到 Clusterfuzz 的服務介面。
後續根據官方文件跑 OpenSSL Heartbleed 的範例在 Docker 內會遇到 `Failed to upload` 的錯誤,但因為後續還要跑 production setup,我就沒有繼續在 Docker 裡面除錯。要跑 OpenSSL Heartbleed 的範例參考後面 Virtual Machine 的說明。
**註**: 前面的步驟如果沒有先跑 `export SHELL=/bin/bash` 應該會 output 出如下的錯誤訊息:
```bash
pipenv.vendor.shellingham._core.ShellDetectionFailure
```
這裡我沒有去深究錯誤的原因,google 一陣子後沒有找到解決方法,丟 ChatGPT 才得到解決方法(指`export SHELL=/bin/bash`)。
### Virtual Machine
這邊我用的是在 VirtualBox 上開一台 x86_64 架構的 Ubuntu20.04 的虛擬機,記得要用有 GUI 介面的 Desktop 版,不要用到 Server 版 (不小心用到的話,也可以後續透過 `sudo apt install ubuntu-desktop` )。
- Check version by:
```bash
$ uname -m
# or
$ cat /etc/os-release
```
確認是 x86_64 架構的機器即可,因為 clusterfuzz 不支援 arm 架構。
指令跟 Docker 相同,但部分指令需要改以 `sudo` 方式執行 (因為 Docker 在建立階段本身就是以管理員身分執行,因此不用額外加 sudo),執行指令如下:
```bash
$ sudo apt-get update -y && \
sudo apt-get install -y build-essential wget curl git gnupg zlib1g-dev libffi-dev libsqlite3-dev libssl-dev lsb-core openssl apt-transport-https ca-certificates vim sudo
$ cd /tmp/ && \
wget https://www.python.org/ftp/python/3.7.7/Python-3.7.7.tgz && \
tar xzf Python-3.7.7.tgz && \
cd Python-3.7.7 && \
sudo ./configure --prefix=/opt/python/3.7.7/ --enable-loadable-sqlite-extensions --with-computed-gotos --with-system-ffi && \
sudo make -j "$(nproc)" && \
sudo make altinstall && \
sudo rm /tmp/Python-3.7.7.tgz && \
sudo ln -s /opt/python/3.7.7/bin/python3.7 /usr/local/bin/python3
$ cd /root/ && \
git clone https://github.com/google/clusterfuzz.git && \
cd clusterfuzz && \
git pull && \
git checkout tags/v2.6.0 && \
sudo ./local/install_deps.bash
```
到這邊所有的套件都已安裝完成,接下來一樣建議先開 `tmux` 並開虛擬環境執行 server:
```bash
$ tmux
$ python3.8 -m pipenv shell
$ python butler.py run_server --bootstrap
```
等待服務成功架設,開另一個終端機視窗,接下來要執行 bot 相關的 script。
```bash
$ python butler.py run_bot --name [bot-name] /path/to/bot-name
# Example
$ python butler.py run_bot --name my-bot /home/username/my-bot
```
這個 script 一樣會持續執行,並在你指定的路徑下新增你所命名的資料夾,以上面這個例子來說,會在 `/home/username` 下新增一個 `/my-bot` 資料夾,裡面存放 bot 相關的資訊。這時在 <http://localhost:9000> 中的 Bots 頁面應該可以看到一個 my-bot 機器人。
這時再多開一個終端機視窗,接下來要看 bot 的 log。
```bash
$ cd /path/to/bot-name/clusterfuzz/bot/log
$ tail -f bot.log
```
這時候因為還沒有任何的 job,所以 bot 不會執行 task,因此會看到如下的錯誤訊息:
```bash
...
...
2023-08-16 18:06:01,795 - run_bot - ERROR - Failed to get any fuzzing tasks. This should not happen.
```
因此接下來就實際照官方文件來跑用 Clusterfuzz 找出 OpenSSL 這個以 C++ 撰寫的套件中的漏洞的範例吧 !
### Heartbleed example
在跑這個範例之前,必須要先下載 clang 編譯器,根據 llvm 官方網站,執行:
```bash
$ bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
```
這個 script 會在 `/usr/bin` 下安裝 clang 相關執行檔 ( `clang-17, clang++-17` ),`17` 為版本編號。接著必須分別把這兩個 clang 相關執行檔設為環境變數:
```bash
# This is just an example, the path depends on where you installed
$ export CC=/usr/bin/clang-17
$ export CXX=/usr/bin/clang++-17
```
接著照官方文件說明執行:
```bash
# Download and unpack a vulnerable version of OpenSSL:
curl -O https://ftp.openssl.org/source/old/1.0.1/openssl-1.0.1f.tar.gz
tar xf openssl-1.0.1f.tar.gz
# Build OpenSSL with ASan and fuzzer instrumentation:
cd openssl-1.0.1f/
./config
# $CC must be pointing to clang binary, see the "compiler section" link above.
make CC="$CC -g -fsanitize=address,fuzzer-no-link"
cd ..
# Download the fuzz target and its data dependencies:
curl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/handshake-fuzzer.cc
curl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/server.key
curl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/server.pem
# Build OpenSSL fuzz target for ClusterFuzz ($CXX points to clang++ binary):
$CXX -g handshake-fuzzer.cc -fsanitize=address,fuzzer openssl-1.0.1f/libssl.a \
openssl-1.0.1f/libcrypto.a -std=c++17 -Iopenssl-1.0.1f/include/ -lstdc++fs \
-ldl -lstdc++ -o handshake-fuzzer
zip openssl-fuzzer-build.zip handshake-fuzzer server.key server.pem
```
過程主要是在編譯有問題的 OpenSSL 套件,接著就可以把這個套件實際上傳到 ClusterFuzz 的介面進行測試。
在 ClusterFuzz 的介面:
1. 前往 Jobs 頁面
2. 找到 “Add New Job“ 的表格,填入下列內容:
- **“libfuzzer_asan_linux_openssl”** for the “Name”.
- **“LINUX”** for the “Platform”.
- **“libFuzzer”** for the “Select/modify fuzzers”.
- **“libfuzzer”** and **“engine_asan”** for the “Templates”.
- `CORPUS_PRUNE = True` for the “Environment String”.
3. 在 Custom Build 欄位選擇剛剛壓縮的 `openssl-fuzzer-build.zip` 檔案上傳
4. 點選 “Add“,完成新增 Job 的工作。
稍微等待幾分鐘後,可以到 Bots 頁面觀看前述步驟新增的 `my-bot`,這時應該會顯示 `fuzz libfuzzer libfuzzer_asan_linux_openssl` 代表已經找到漏洞。回到 `/my-bot/clusterfuzz/bot/logs` 中看 `bot.log` 這個檔案的 outpup:
```bash
$ cat bot.log
...
...
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/cdxvy30/my-bot/clusterfuzz/bot/builds/libfuzzer_asan_linux_openssl/custom/handshake-fuzzer+0x281079) (BuildId: 425751f4a59f9763ff45351813695bb53fb9949c)
Shadow bytes around the buggy address:
0x529000009480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x529000009500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x529000009580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x529000009600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x529000009680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x529000009700: 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa
0x529000009780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x529000009800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x529000009880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x529000009900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x529000009980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==14754==ABORTING
MS: 1 ShuffleBytes-; base unit: 0ba0c1311882a3a494570d4b26201ca3c5b2e675
0x18,0x3,0x0,0x0,0x1,0x1,0x0,0x3,0x0,0xa7,0xa7,0x3,0x1,0x0,0x0,0x1,0x3,0x2,0x0,0x3,0x7a,0xff,0x0,
\030\003\000\000\001\001\000\003\000\247\247\003\001\000\000\001\003\002\000\003z\377\000
artifact_prefix='/home/cdxvy30/my-bot/clusterfuzz/bot/inputs/fuzzer-testcases/'; Test unit written to /home/cdxvy30/my-bot/clusterfuzz/bot/inputs/fuzzer-testcases/crash-0970e17d686ca8e34447447f3864c04148e25a58
Base64: GAMAAAEBAAMAp6cDAQAAAQMCAAN6/wA=
stat::number_of_executed_units: 18700
stat::average_exec_per_sec: 0
stat::new_units_added: 65
stat::slowest_unit_time_sec: 0
stat::peak_rss_mb: 137
...
...
```
代表已經成功找出 OpenSSL 這個函式庫中的漏洞。
### Integrate with Golang
因在 Go Team 的專案裡面,要測試的對象是以 Golang 撰寫,所以必須整合上傳 Golang 的功能。由於 ClusterFuzz 吃的是 libFuzzer 介面的檔案,而參考這個 [issue](https://github.com/google/clusterfuzz/issues/860) 中提到有專門編譯 Golang 成 libFuzzer 介面的[專案](https://github.com/dvyukov/go-fuzz),看起來可用於整合 Golang 檔案上傳至 ClusterFuzz。
## Production Setup
設定 Production 環境會用到 Google Cloud Platform 的服務,因此需要先準備一個還有免費額度的 GCP 帳號。另外因為必須要通過 Google OAuth 驗證,而在 Docker 中沒有 GUI 介面,無法透過瀏覽器進行 Google 的 OAuth 驗證,因此這部分目前只能用 Virtual machine 進行設定。
### Virtual Machine
接續 Local Setup 的步驟,接下來需要到 Google Cloud Platform 上新增一些設定。這部分[官方文件](https://google.github.io/clusterfuzz/production-setup/clusterfuzz/)寫的滿詳細,主要跟著指示依序在 GCP, Firebase 上做設定並 export 環境變數:
```bash
# Login to your Google account
$ gcloud auth application-default login
$ gcloud auth login
# Verify that your project is successfully created
$ gcloud projects describe <your project id>
# Export the project id in environment for later use
$ export CLOUD_PROJECT_ID=<your project id>
# Enable Firebase
# Copy the apiKey value, and export it
$ export FIREBASE_API_KEY=<your api key>
# Create OAuth credentials, and export it
$ export CLIENT_SECRETS_PATH=/path/to/your/client_secrets.json
# Create config directory
mkdir /path/to/yourconfig # Any EMPTY directory outside the ClusterFuzz source repository.
export CONFIG_DIR=/path/to/yourconfig
```
最後執行:
```bash
$ python butler.py create_config --oauth-client-secrets-path=$CLIENT_SECRETS_PATH \
--firebase-api-key=$FIREBASE_API_KEY --project-id=$CLOUD_PROJECT_ID $CONFIG_DIR
```
注意執行這個 script 後會跳出瀏覽器視窗要做 OAuth 驗證,記得選前面設定的 Google 帳戶即可。
### Google Cloud setup
特別注意根據官方 script 跑 config 會遇到 `clouddebugger.googleapis.com` 這個功能無法啟用的情況,需要手動進入 config 的檔案把該服務註解掉,因為該服務已經在 2023/05/29 終止服務,而在 script 裡面會一直去要求 enable 該服務導致出錯。
### Python version check
有可能會遇到 Python version check 的錯誤,如果遇到的話,跟著錯誤訊息中提到的檔案,把對應的 version check 註解掉即可。
### Verification
完成上述步驟之後,可以到 `https://<your project id>.appspot.com` 確認是否可以看到 ClusterFuzz 的頁面。如果可以的話就代表服務有架設起來了,也可以到個人的 Google Cloud Console 去看 App Engine 等頁面,應該會看到一些相關資訊。
### 小結
目前服務雖然有架設起來,但是 bot 沒有成功設置,目前還在 debug 中。目前 Production Setup 過程遇到的問題如下:
#### grpc
- 實際進入執行 ClusterFuzz 任務的 bot (linux-vm) 上看,clusterfuzz 的部署行為大致是:
- 開機後執行一些前置作業包括設定 username、環境變數、swappiness、iptables 的 health check等
- 實際執行 clusterfuzz.service 這個服務,應該是透過 `systemctl start`
- 這裡會把官方的 image (gcr.io/clusterfuzz-images/base) pull 下來
- `docker run` 把 host 剛剛從 `gs://deployment.omega-dahlia-395309.appspot.com` 上下載下來的 `linux-3.zip` unzip 掛到容器內部然後執行初始化腳本 (`/docker/base/*.sh`)
- 跑到 `grpc` 相關的 `import` 時出錯
- 嘗試從 `/base/Dockerfile` 除錯,重新 build image,主要想重新下載 grpc library 到它指定的 PYTHONPATH。
- 解決: 把 dockerfile 中的 RUN_CMD python3.7 改成 3.8,重新設定後不用改。
- 其他: `init.yaml` 有改的話,version 數字要自行加 1,否則 VM 只會重啟不會重新。initialize
- 參考:
- https://github.com/google/clusterfuzz/issues/253
- https://github.com/google/clusterfuzz/issues/252
#### download_from_string is broken
- `grpc` 的問題修正之後,GCP 上的 VM 可正常運作,但在上傳要進行模糊測試的檔案到 GCS 之後,bot 有顯示在執行 job,但其實進到 VM 的 log 裡面看,沒有看到顯示在測試的 log。這裡我一開始因為看到這個 [issue](https://github.com/google/clusterfuzz/issues/3257),就想著要去改原始碼,但其實這個方向不對,因為我測試是用 2023 年 2 月 release 的 `2.6.0` 版,而 issue 的原因是 2023 年 7 月之後 `google-cloud-sdk` 有個 method deprecated ,所以應該不是這個問題造成錯誤。而後續尚未針對此問題進行 debug。
## Other Tracking Issues
1. [Replace download_as_string with download_as_bytes](https://github.com/google/clusterfuzz/issues/3257)
2. [Fix prerequisites documentation](https://github.com/google/clusterfuzz/issues/3289)
3. [Upgrade Python](https://github.com/google/clusterfuzz/issues/3218)
4. [Upload error when adding a job](https://github.com/google/clusterfuzz/issues/2241)
## My Current ClusterFuzz Codebase and Configuration
1. https://github.com/cdxvy30/clusterfuzz
2. https://github.com/cdxvy30/clusterfuzz-config