---
# System prepended metadata

title: ClusterFuzz Environment

---

# 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