概要
メモリのオーバフローテストなどできます
環境
- Ubuntu 24.04
- Python 3.12.10
- atheris 3.0.0
テスト対象のコード
-
vim fuzzing.py
import ipaddress
def get_ipaddress(data: dict) -> str | None:
"""
入力のdictから 'ipaddress' キーの値を取得して返却する。
Args:
data: 入力辞書
Returns:
str | None: 有効なIPアドレス、またはキーが存在しない場合はNone
Raises:
ValueError: IPアドレスのフォーマットが不正な場合
"""
if "ipaddress" not in data:
return None
ip_str = data["ipaddress"]
# IPアドレスのフォーマットをチェック
try:
# IPv4 または IPv6 アドレスとして検証
ipaddress.ip_address(ip_str)
return ip_str
except ValueError as e:
raise ValueError(f"Invalid IP address format: {ip_str}") from e
テストコード
- vim test_fuzzing.py
import sys
import atheris
from fuzzing import get_ipaddress
@atheris.instrument_func
def test_get_ipaddress(data):
"""
get_ipaddress関数のファジングテスト
"""
if len(data) < 1:
return
# ランダムなバイトデータを文字列に変換
try:
input_str = data.decode('utf-8', errors='ignore')
except Exception:
return
# テストケース1: キーが存在しない場合
try:
result = get_ipaddress({})
assert result is None
except Exception:
pass
# テストケース2: ipaddressキーに様々な値を設定
try:
result = get_ipaddress({"ipaddress": input_str})
# 結果が返ってきた場合は文字列であることを確認
if result is not None:
assert isinstance(result, str)
except ValueError:
# フォーマットエラーは想定内
pass
except Exception as e:
# その他の予期しない例外は記録
print(f"Unexpected exception: {type(e).__name__}: {e}")
raise
# テストケース3: 他のキーも含む辞書
try:
test_dict = {
"other_key": "value",
"ipaddress": input_str,
"another_key": 123
}
result = get_ipaddress(test_dict)
if result is not None:
assert isinstance(result, str)
except ValueError:
pass
except Exception as e:
print(f"Unexpected exception in test case 3: {type(e).__name__}: {e}")
raise
def main():
"""
ファジングテストのエントリーポイント
"""
atheris.Setup(sys.argv, test_get_ipaddress)
atheris.Fuzz()
if __name__ == "__main__":
main()
おまけ: クラスの場合
import ipaddress
class IPAddressValidator:
"""IPアドレスの検証を行うクラス"""
def get_ipaddress(self, data: dict) -> str | None:
"""
入力のdictから 'ipaddress' キーの値を取得して返却する。
Args:
data: 入力辞書
Returns:
str | None: 有効なIPアドレス、またはキーが存在しない場合はNone
Raises:
ValueError: IPアドレスのフォーマットが不正な場合
"""
if "ipaddress" not in data:
return None
ip_str = data["ipaddress"]
# IPアドレスのフォーマットをチェック
try:
# IPv4 または IPv6 アドレスとして検証
ipaddress.ip_address(ip_str)
return ip_str
except ValueError as e:
raise ValueError(f"Invalid IP address format: {ip_str}") from e
import sys
import atheris
from fuzzing import IPAddressValidator
class FuzzingTest:
"""IPAddressValidatorのファジングテストクラス"""
def __init__(self):
self.validator = IPAddressValidator()
@atheris.instrument_func
def test_get_ipaddress(self, data):
"""
get_ipaddressメソッドのファジングテスト
"""
if len(data) < 1:
return
# ランダムなバイトデータを文字列に変換
try:
input_str = data.decode("utf-8", errors="ignore")
except Exception:
return
# テストケース1: キーが存在しない場合
try:
result = self.validator.get_ipaddress({})
assert result is None
except Exception:
pass
# テストケース2: ipaddressキーに様々な値を設定
try:
result = self.validator.get_ipaddress({"ipaddress": input_str})
# 結果が返ってきた場合は文字列であることを確認
if result is not None:
assert isinstance(result, str)
except ValueError:
# フォーマットエラーは想定内
pass
except Exception as e:
# その他の予期しない例外は記録
print(f"Unexpected exception: {type(e).__name__}: {e}")
raise
# テストケース3: 他のキーも含む辞書
try:
test_dict = {
"other_key": "value",
"ipaddress": input_str,
"another_key": 123,
}
result = self.validator.get_ipaddress(test_dict)
if result is not None:
assert isinstance(result, str)
except ValueError:
pass
except Exception as e:
print(f"Unexpected exception in test case 3: {type(e).__name__}: {e}")
raise
def main():
"""
ファジングテストのエントリーポイント
"""
fuzzing_test = FuzzingTest()
atheris.Setup(sys.argv, fuzzing_test.test_get_ipaddress)
atheris.Fuzz()
if __name__ == "__main__":
main()
おまけ: pytest で書く
なさそうです
最後に
テスト中は CPU 100% になるので注意しましょう
かなり負荷がかかるテストをするのでデータベースや外部にリクエストする関数をテストする場合は必ずモックするようにしましょう
またテストの量も膨大で上記のコードでも30分くらいかかりました
速く終わらせたい場合はスペックのあるマシン上でテストしてください
0 件のコメント:
コメントを投稿