Cloud Functions実行環境で「rm -rf /*」を実行してみた

FaaSなら事故対策もバッチリです
2021.12.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

MAD事業部@大阪の岩田です。

本エントリは クラスメソッド Google Cloud Advent Calendar 2021 の 24日目 の記事です。

最近色んな環境でrm -rf /*を実行するのが流行っているようですね。

このビッグウェーブに乗るべくGoogle CloudのCloud Functions実行環境でrm -rf /*を試してみたいと思います。

やってみる

早速やっていこうと思うのですが、OSコマンドの実行を試すためにいちいちCloud Functionsにデプロイするソースコードを書き換えてデプロイして...を繰り返すのは効率が悪そうです。理想としてはCloud Functions実行環境にシェルアクセスして各種コマンドで環境を自由に分析し、最後にrm -rf /*を実行できると嬉しいです。

ということで、以前紹介したserverless-preyを利用してCloud Functions実行環境にシェルアクセスできる環境を構築します。

Cheetahのデプロイ

serverless-preyはnetcatとngrokを利用して各FaaSプラットフォーム向けにシェルアクセスを提供する関数のコレクションです。Google CloudのCloud Functions向けにはCheetahを利用します。

まずはGitHubからserverless-preyのリポジトリをクローンします

$ git clone https://github.com/pumasecurity/serverless-prey.git

続いて以下のコマンドでCheetahをデプロイします

$ cd serverless-prey/cheetah/src/cheetah/
$ gcloud functions deploy cheetah --entry-point Cheetah --runtime go111 --trigger-http --memory=8192MB  --timeout=540

ランタイムにはGo 1.11を指定しています。現在は非推奨のバージョンですが、1.13に上げてしまうとCheetahが動作しません。メモリとタイムアウト値はrm -rf/*の実行時間を考慮して最大値を指定しておきます。

以下のように出力されればデプロイ完了です。

WARNING: The go111 runtime is deprecated on Cloud Functions. Please migrate to a newer Golang version (--runtime=go113).
Allow unauthenticated invocations of new function [cheetah]? (y/N)?

WARNING: Function created with limited-access IAM policy. To enable unauthorized access consider `gcloud alpha functions add-iam-policy-binding cheetah --region=us-central1 --member=allUsers --role=roles/cloudfunctions.invoker`
Deploying function (may take a while - up to 2 minutes)...⠛
For Cloud Build Logs, visit: https://console.cloud.google.com/cloud-build/builds;region=us-central1/05f0d094-36e0-40cf-9a55-d21da4eeb3ff?project=<GCPのプロジェクトID>
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 8192
buildId: 05f0d094-36e0-40cf-9a55-d21da4eeb3ff
buildName: projects/<GCPのプロジェクトID>/locations/us-central1/builds/05f0d094-36e0-40cf-9a55-d21da4eeb3ff
entryPoint: Cheetah
httpsTrigger:
  securityLevel: SECURE_ALWAYS
  url: https://us-central1-<GCPのプロジェクト名>.cloudfunctions.net/cheetah
ingressSettings: ALLOW_ALL
labels:
  deployment-tool: cli-gcloud
name: projects/GCPのプロジェクト名/locations/us-central1/functions/cheetah
runtime: go111
serviceAccountEmail: <GCPのプロジェクト名>@appspot.gserviceaccount.com
sourceUploadUrl: https://storage.googleapis.com/gcf-upload-us-central1-8c61bdd9-3f4a-4b01-aaf6-ed74d76df6f0/9d6e145d-4944-499b-a753-ad0f79bbbb99.zip
status: ACTIVE
timeout: 540s
updateTime: '2021-12-21T13:04:30.319Z'
versionId: '1'

Cloud Functions実行環境にシェルアクセスしてみる

関数の準備ができたのでserverless-preyのスクリプトを使ってCloud Functions実行環境にシェルアクセスしてみます。※serverless-preyはncとngrokを利用するので両コマンドを事前にインストールしておいて下さい。

まず関数を呼び出すために必要なIDトークンを取得します。

$export GCLOUD_ID_TOKEN=$(gcloud config config-helper --format=json | jq -r '.credential.id_token')

取得したIDトークンを使って関数を起動しつつngrokとncを使って自分のローカル環境からシェルアクセスしましょう。./script/prey.shというシェルスクリプトが諸々の処理を実行してくれるので、このスクリプトに関数のトリガーURLとIDトークンを渡して関数を起動します。

$ ./script/prey.sh cheetah --url <デプロイした関数のトリガーURL> --api-key $GCLOUD_ID_TOKEN

しばらくすると以下のように出力されプロンプトの>が表示されます。

  @@@@@@@@((.                                                        ((@@@@@@@@
   *@@@@@@(/*#@%,                                               %&@(*/(@@@@@@/
     (@@@@@(*.#%&@*/(                                       //*@&%#.*(@@@@@(
       /@@@@@(***//(&&&%.                               .%&&&(//***(@@@@@%
          #&@@@@@@&&&&&&&&&                           %&&&&&&&&@@@@@@&#

                                 Serverless Prey
                 https://github.com/pumasecurity/serverless-prey

>

これで...Cloud Functions実行環境でコマンド実行し放題!!まずはウォーミングアップでいくつかコマンドを実行してみましょう

ls
go.mod
go.sum
main.go
serverless_function_source_code

pwd
/workspace

whoami
www-data

uname -a
Linux localhost 4.4.0 #1 SMP Sun Jan 10 15:06:54 PST 2016 x86_64 x86_64 x86_64 GNU/Linux

hostname
localhost

ip a
2: eth0: <UP,LOWER_UP> mtu 65000
    link/none 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 169.254.8.130/32 scope global dynamic
3: eth1: <UP,LOWER_UP> mtu 1280
    link/none 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 169.254.8.1/32 scope global dynamic
    inet6 fddf:3978:feb1:d745::c001/128 scope global dynamic
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536
    link/loopback 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    inet6 ::1/128 scope global dynamic
    inet 127.0.0.1/8 scope global dynamic

dmesg
[    0.000000] Starting gVisor...
[    0.201683] Creating cloned children...
[    0.376652] Checking naughty and nice process list...
[    0.622287] Moving files to filing cabinet...
[    0.989163] Mounting deweydecimalfs...
[    1.263683] Constructing home...
[    1.645722] Digging up root...
[    1.803795] Reticulating splines...
[    2.065709] Consulting tar man page...
[    2.263922] Checking naughty and nice process list...
[    2.331320] Creating process schedule...
[    2.814881] Ready!

なるほどなるほど。ウォーミングアップ完了です。

rm -rf /*を実行してみる

ウォーミングアップできたら本題であるrm -rf /*を実行しましょう。自分が操作しているシェルがCloud Functions実行環境のシェルであることを入念にチェックしてから実行して下さい

rm -rf /*
rm: cannot remove '/bin/bash': Read-only file system
rm: cannot remove '/bin/bunzip2': Read-only file system
rm: cannot remove '/bin/bzcat': Read-only file system
rm: cannot remove '/bin/bzcmp': Read-only file system
rm: cannot remove '/bin/bzdiff': Read-only file system
rm: cannot remove '/bin/bzegrep': Read-only file system
rm: cannot remove '/bin/bzexe': Read-only file system
rm: cannot remove '/bin/bzfgrep': Read-only file system
rm: cannot remove '/bin/bzgrep': Read-only file system
rm: cannot remove '/bin/bzip2': Read-only file system
rm: cannot remove '/bin/bzip2recover': Read-only file system
rm: cannot remove '/bin/bzless': Read-only file system
rm: cannot remove '/bin/bzmore': Read-only file system
rm: cannot remove '/bin/cat': Read-only file system
...略

動き始めました。リアルタイムに出力を確認できるのがシェルアクセスの良いところですね。肝心の実行結果は?というと...予想通りですが権限が無いため何も削除できていないようです。

ドキュメント上の明確な記述が見つけられなかったのですが、各種情報を調べた限り、Cloud Functions実行環境で書き込みが許可されているのは/tmpディレクトリ以下のみのようです。このあたりはAWS Lambdaのような他のFaaSでも同様ですね。

しばらく待つと以下のような出力で止まってしまいました。

rm: cannot remove '/workspace/serverless_function_source_code/Makefile': Read-only file system
rm: cannot remove '/workspace/serverless_function_source_code/cheetah.go': Read-only file system
rm: cannot remove '/workspace/serverless_function_source_code/cheetah.yaml': Read-only file system
rm: cannot remove '/workspace/serverless_function_source_code/go.mod': Read-only file system
rm: cannot remove '/workspace/serverless_function_source_code/go.sum': Read-only file system
rm: cannot remove '/workspace/serverless_function_source_code/package-lock.json': Read-only file system
rm: cannot remove '/workspace/serverless_function_source_code/package.json': Read-only file system
rm: cannot remove '/workspace/serverless_function_source_code/resources.yml': Read-only file system
rm: cannot remove '/workspace/serverless_function_source_code/serverless.yml': Read-only file system
rm: cannot remove '/www-data-home': Read-only file system

Connection terminated.

関数のタイムアウト値は540秒に設定していたのですが、1分も待たずに出力が止まったのが意外なところでした。Cloud Loggingのログを確認すると以下のように出力されていました。約332秒後に500エラーで終了しているようです。

Function execution took 332805 ms, finished with status code: 500

試しに実行するコマンドをrm -rf /*からsleep 540に変えてみたところCloud Loggingのログは以下のように出力されました。

Function execution took 540003 ms, finished with status: 'timeout'

こちらはタイムアウト値の540秒とほぼイコールですね。なぜrm -rf /* の場合はタイムアウト時間を待たずに異常終了してしまったのかは謎のままです。。。

まとめ

Cloud Functionsの実行環境でrm -rf /*を実行しても何も削除されないことが分かりました。FaaSという特性を考えれば当然の結果と言えるでしょう。実際には自分で/tmp配下に書き込んだファイルであれば削除可能ですが、まあ所詮は/tmpなので実行環境が壊れることはありません。うっかりrm -rf /*しても大丈夫なFaaSは安心して使えるコンピューティング基盤と言えますね。(まあ普通はFaaSの実行環境にシェルアクセスしたりコードからrm -rf /*することは無いのですが...)

アドベントカレンダー最終日である明日 12/25 は 田中孝明 さんによる「何かしらの手段でクリスマスを爆発させる(仮」です。私はCloud Functions実行環境の爆発に失敗してしまいましたが、田中さんには期待したいところです。お楽しみに!