
35
Забудь про выделенный кластер за $50к в месяц – можно поднять фаззинг-ферму на serverless-инфраструктуре, где 10 000 параллельных Lambda-инстансов молотят по таргету одновременно, а платишь только за реальное время выполнения.
Почему Lambda вообще подходит для фаззинга
Главный козырь AWS Lambda – это massively parallel invocation: множество инстансов одной и той же функции могут выполняться конкурентно без ручного управления инфраструктурой. AWS Step Functions Distributed Map официально поддерживает до 10 000 конкурентных выполнений Map-состояния, что и даёт то самое число “10K ядер” в заголовке – это не маркетинг, а конкретный технический лимит сервиса. По сути каждый Lambda-инвок – это изолированный контейнер с CPU и памятью, который отлично ложится на модель фаззинга: один воркер = один shard корпуса на короткую кампанию.
Академические и open-source прецеденты уже есть: LibAFL, написанный на Rust, изначально проектировался для superior scalability across multiple cores and machines и может выступать drop-in заменой libFuzzer именно ради горизонтального масштабирования. Google ClusterFuzz, кстати, использует похожую идею массового распределённого запуска fuzzer-джобов (libFuzzer, AFL++, honggfuzz) через job-очереди, просто на своей инфраструктуре, а не на AWS.google.
Архитектура пайплайна
Схема строится на четырёх компонентах: оркестрация, доставка полезной нагрузки, сами fuzzer-воркеры и агрегация результатов.
Практическая деталь: AFL++ имеет стабильную поддержку multi-core fuzzing кампаний из коробки, что упрощает шардинг корпуса между Lambda-инстансами – каждому воркеру просто нарезаешь свой независимый seed-набор.
Минимальный воркер на Python + boto3
Вот скелет диспетчера, который асинхронно бросает N инвокаций Lambda с разными shard’ами корпуса из S3:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import boto3 import json lambda_client = boto3.client('lambda') def dispatch_fuzz_shards(function_name, corpus_shards, target_binary_s3_key): for shard_id, shard_key in enumerate(corpus_shards): payload = { "shard_id": shard_id, "corpus_s3_key": shard_key, "target_s3_key": target_binary_s3_key, "timeout_sec": 840, "output_bucket": "fuzz-crashes-bucket" } lambda_client.invoke( FunctionName=function_name, InvocationType='Event', Payload=json.dumps(payload) ) def lambda_handler(event, context): import subprocess shard_id = event["shard_id"] subprocess.run([ "./afl-fuzz", "-i", f"/tmp/corpus_{shard_id}", "-o", "/tmp/out", "-V", str(event["timeout_sec"]), "--", "./fuzz_target" ]) |
InvocationType='Event' – ключевой момент: асинхронный вызов не блокирует диспетчер и позволяет забросить тысячи инвокаций за секунды, а Lambda сама распределит их по доступной ёмкости.
Ограничения, о которых молчат туториалы
Главный подводный камень – 15-минутный жёсткий лимит времени выполнения Lambda, из-за чего длинные fuzzing-кампании превращаются в цепочку коротких сессий с checkpoint’ами в S3, а не в один непрерывный прогон. Второй момент – concurrency limit аккаунта по умолчанию куда ниже 10 000, и его нужно поднимать через AWS Support заранее, иначе получишь throttling вместо фаззинга. Есть и трюк “cheating scalability” – traffic isolation и мультирегиональное дублирование функции под одной и той же логикой, что даёт N-кратный прирост параллелизма без переписывания кода.
Отдельная проблема – сама природа фаззинга: throughput одного и того же fuzzer на одном таргете может варьироваться в разы (до 20.9x) между запусками из-за стохастичности, поэтому 10 000 параллельных shard’ов – это не гарантия линейного прироста находок, а способ статистически “перебить” эту случайность объёмом.
Куда это движется
Свежие исследования вроде фреймворка Orion показывают тренд на комбинирование LLM-агентов с детерминированными фаззинг-инструментами: LLM берёт на себя семантический анализ кода и генерацию гипотез, а традиционный fuzzer – верификацию, что сокращает человеческие трудозатраты в 46–204 раза и уже нашло две 0-day в библиотеке clib. Логичный next step для твоей Lambda-фермы – прикрутить LLM-triage слой поверх найденных крашей, чтобы не разгребать тысячи дублирующихся stack trace вручную.