Фингерпринтинг глубины стека: лимиты рекурсии
Как глубина стека JavaScript и лимиты рекурсии варьируются по браузерам и платформам для создания отпечатков, и как контролировать поведение стека.
Нужна поддерживаемая продуктовая документация?
У этой статьи есть соответствующая страница в центре документации. Используйте docs для каноничного сценария настройки, актуальных флагов и долгосрочной справки.
Введение
Каждый движок JavaScript имеет ограничение на глубину рекурсии функций. Когда функция вызывает себя слишком много раз, движок выбрасывает ошибку "Maximum call stack size exceeded". Этот лимит не стандартизирован. Он различается между браузерами, версиями браузеров, операционными системами и даже между основным потоком и Web Workers в одном браузере. Системы отслеживания используют эту вариацию, измеряя, сколько рекурсивных вызовов возможно до переполнения стека. Результирующее число, максимальная глубина рекурсии, становится сигналом отпечатка. Поскольку это значение зависит от низкоуровневых внутренностей движка и платформенно-специфичного выделения стека, его трудно контролировать из JavaScript. Эта статья объясняет, как работает снятие отпечатков по глубине стека и как BotBrowser обеспечивает точный контроль через флаг --bot-stack-seed.
Влияние на конфиденциальность
Снятие отпечатков по глубине стека является одной из более малоизвестных техник, но она предоставляет полезную энтропию именно потому, что большинство инструментов конфиденциальности её не адресуют. Исследования академических групп, изучающих снятие отпечатков браузера, показали, что лимиты рекурсии могут различать версии браузеров, операционные системы и даже 32-битные vs. 64-битные сборки одного браузера.
Техника эффективна потому, что:
- Различается между браузерами. Chrome, Firefox и Safari каждый выделяют разные размеры стека по умолчанию, производя разные максимальные глубины рекурсии.
- Различается между платформами. Одна версия браузера на Windows, macOS и Linux может сообщать разные глубины из-за различий выделения стека на уровне ОС.
- Различается между контекстами. Основной поток, выделенные workers, общие workers и модули WASM каждый имеют разные лимиты стека внутри одного браузера.
- Стабильна. На одной комбинации браузер/ОС/оборудование лимит рекурсии постоянен между сессиями.
Исследование от команды браузера Brave отметило, что глубина стека предоставляет примерно 2-3 бита энтропии. Хотя это скромно, это ценно в составных отпечатках, потому что коррелирует с внутренностями движка браузера, которые другие сигналы не охватывают. Несоответствие между заявленным пользовательским агентом и наблюдаемой глубиной стека является сильным сигналом несогласованности.
Техническая основа
Как работает глубина стека JavaScript
При вызове JavaScript-функции движок помещает новый фрейм в стек вызовов. Каждый фрейм содержит локальные переменные функции, параметры и адрес возврата. Стек вызовов занимает область памяти фиксированного размера. Когда стек заполнен, движок выбрасывает RangeError.
Максимальная глубина зависит от:
- Выделения размера стека. ОС выделяет определенный объем памяти для стека. Linux по умолчанию 8 МБ (настраивается через
ulimit -s), macOS выделяет 8 МБ для основного потока и 512 КБ для вторичных потоков, Windows по умолчанию 1 МБ. - Размера фрейма. Каждый фрейм вызова имеет разный размер в зависимости от количества локальных переменных и внутреннего учёта движка. Функция без локальных переменных использует меньше стекового пространства на фрейм, чем функция с множеством переменных.
- Оптимизаций движка. V8 (Chrome), SpiderMonkey (Firefox) и JavaScriptCore (Safari) каждый реализуют стек вызовов по-разному, с разными размерами заголовков, требованиями выравнивания и проходами оптимизации.
- Контекста выполнения. Основной поток обычно имеет больший стек, чем Web Workers. Модули WASM могут иметь собственную конфигурацию стека.
Измерение глубины стека
Скрипт отслеживания измеряет глубину стека, подсчитывая рекурсивные вызовы до возникновения исключения:
function measureDepth() {
let depth = 0;
function recurse() {
depth++;
recurse();
}
try {
recurse();
} catch (e) {
return depth;
}
}
Результат зависит от размера фрейма функции. Минимальная функция (без локальных переменных, без аргументов) производит большее число, чем функция с множеством переменных, но оба значения стабильны для данной комбинации браузер/ОС.
Вариация между контекстами
Измерения глубины стека различаются между контекстами выполнения:
- Основной поток обычно имеет наибольший стек (часто 8 МБ или более).
- Выделенные Workers имеют меньшие стеки (часто 1 МБ или 512 КБ).
- Модули WASM могут иметь собственные лимиты стека, отдельные от JavaScript.
Эти различия согласованы внутри комбинации браузер/ОС, но варьируются между комбинациями. Соотношение между глубиной основного потока и глубиной worker само по себе является отличительным сигналом.
Почему существующие защиты не работают
Глубина стека определяется внутренним управлением стека движка JavaScript. Её нельзя модифицировать из JavaScript, потому что:
- Исключение выбрасывается движком, а не кодом JavaScript.
- Размер стека выделяется ОС при создании потока.
- Перехват RangeError не меняет глубину, на которой он возникает.
- Расширения браузера работают в том же движке и не имеют доступа к конфигурации стека.
Распространенные подходы к защите и их ограничения
Практически не существует защит на уровне JavaScript от снятия отпечатков по глубине стека. Значение определяется движком и ОС, и никакое расширение или скрипт не может его изменить.
Подмена пользовательского агента меняет заявленный браузер, но не меняет фактическое поведение движка. Заявление о Firefox при работе в Chrome не меняет специфичную для Chrome глубину стека, создавая обнаруживаемое несоответствие.
Виртуальные машины могут изменить глубину стека (из-за различных конфигураций ОС), но вводят другие сигналы снятия отпечатков.
Компиляция пользовательских сборок браузера с модифицированными размерами стека возможна, но непрактична для большинства пользователей. Она также требует сопоставления всех других сигналов уровня движка для поддержания согласованности.
Рандомизация измерения путем раннего перехвата исключений не помогает. Скрипт отслеживания может вызвать функцию измерения несколько раз и взять максимум, или использовать функции разной формы для триангуляции фактического лимита.
Единственный эффективный подход - контроль фактического поведения стека движка, что требует модификации на уровне браузера.
Подход BotBrowser на уровне движка
BotBrowser обеспечивает прямой контроль поведения глубины стека JavaScript через флаг --bot-stack-seed.
Три режима контроля
Флаг --bot-stack-seed принимает три типа значений:
profile - соответствие точной глубине стека профиля. Лимит рекурсии соответствует тому, что произвело бы профилированное устройство. Это наиболее точный вариант для поддержания согласованной идентичности устройства.
real - использование нативной глубины стека. Никакая модификация не применяется. Лимит рекурсии отражает ваше фактическое оборудование и конфигурацию ОС.
Целочисленный сид (1-UINT32_MAX) - генерация детерминированной вариации глубины стека для сессии. Каждый сид производит различное, но стабильное значение глубины. Это полезно, когда нужны отличающиеся сессии, каждая с согласованной глубиной стека, но отличающейся от других.
Покрытие нескольких контекстов
Контроль глубины стека BotBrowser применяется ко всем контекстам выполнения JavaScript:
- Лимиты рекурсии основного потока контролируются.
- Web Workers (выделенные и общие) имеют контролируемые глубины стека независимо, но согласованно с профилем.
- Поведение стека модулей WASM также контролируется.
Соотношение между глубиной основного потока и глубиной workers соответствует тому, что продемонстрировало бы профилированное устройство, обеспечивая согласованные результаты кросс-контекстных измерений.
Согласованность с другими сигналами
Контролируемая глубина стека соответствует пользовательскому агенту, платформе и версии браузера профиля. Если профиль указывает Chrome 120 на Windows 10, глубина стека соответствует тому, что фактически производит Chrome 120 на Windows 10. Нет несоответствия между заявлением пользовательского агента и наблюдаемым поведением движка.
Вариация на основе сида
При использовании целочисленного сида глубина стека детерминированно выводится из значения сида. Один сид всегда производит одну глубину. Разные сиды производят разные глубины в реалистичном диапазоне для профилированной комбинации браузер/ОС. Это позволяет создавать несколько различных идентичностей, каждая с правдоподобной глубиной стека.
Настройка и использование
Глубина стека, соответствующая профилю
chrome --bot-profile="/path/to/profile.enc" \
--bot-stack-seed=profile \
--user-data-dir="$(mktemp -d)"
Вариация на основе сида
# Детерминированная глубина стека от сида
chrome --bot-profile="/path/to/profile.enc" \
--bot-stack-seed=42 \
--user-data-dir="$(mktemp -d)"
# Другая глубина с другим сидом
chrome --bot-profile="/path/to/profile.enc" \
--bot-stack-seed=43 \
--user-data-dir="$(mktemp -d)"
Нативная глубина стека
chrome --bot-profile="/path/to/profile.enc" \
--bot-stack-seed=real \
--user-data-dir="$(mktemp -d)"
В сочетании с другими детерминированными флагами
chrome --bot-profile="/path/to/profile.enc" \
--bot-stack-seed=profile \
--bot-noise-seed=42 \
--bot-time-seed=42 \
--user-data-dir="$(mktemp -d)"
Интеграция с Playwright
const { chromium } = require('playwright');
const browser = await chromium.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-stack-seed=profile'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
// Измерение глубины стека
const depth = await page.evaluate(() => {
let d = 0;
function r() { d++; r(); }
try { r(); } catch(e) {}
return d;
});
console.log(`Stack depth: ${depth}`);
Интеграция с Puppeteer
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
executablePath: '/path/to/botbrowser/chrome',
defaultViewport: null,
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-stack-seed=profile'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
Верификация
Измерение глубины. Запустите рекурсивную функцию в консоли браузера и запишите глубину. Сравните между сессиями с тем же профилем и сидом стека. Глубина должна быть идентичной.
Измерение глубины в Worker. Запустите тот же тест рекурсии в Web Worker. Глубина должна отличаться от основного потока (workers имеют меньшие стеки), но быть согласованной между сессиями.
Кросс-машинная верификация. Запустите тот же тест на другой машине с тем же профилем и сидом. Глубина стека должна совпадать.
Проверка вариации сида. Измените сид и убедитесь, что глубина изменилась. Это подтверждает, что сид активно контролирует поведение.
Лучшие практики
- Используйте
--bot-stack-seed=profileдля максимальной точности. Это соответствует точной глубине стека профилированного устройства, обеспечивая согласованность с пользовательским агентом и платформой. - Комбинируйте с
--bot-noise-seedи--bot-time-seed. Глубина стека, шум рендеринга и поведение таймингов все являются частью общего отпечатка браузера. Контролируйте все три для комплексной защиты. - Не устанавливайте нереалистичные значения глубины. При использовании сида генерируемая глубина попадает в реалистичный диапазон для профилированного браузера. Ручное указание экстремальных значений может быть несогласованно с другими сигналами.
- Тестируйте в контекстах workers. Глубина стека различается между основным потоком и workers. Проверяйте оба.
- Понимайте область применения. Глубина стека является одним сигналом среди многих. Она вносит вклад в общий отпечаток, но недостаточна сама по себе. Всегда используйте полный профиль.
FAQ
Вопрос: Сколько энтропии дает глубина стека? Ответ: Примерно 2-3 бита сама по себе. Значение различает семейства браузеров, типы платформ и иногда конкретные версии. Его ценность возрастает в составных отпечатках, где другие сигналы контролируются.
Вопрос: Меняется ли глубина стека при обновлениях браузера? Ответ: Иногда. Крупные изменения движка V8 могут изменить размер фрейма или выделение стека, что меняет максимальную глубину рекурсии. Это одна из причин, почему профили следует обновлять для соответствия текущим версиям браузера.
Вопрос: Может ли веб-сайт измерить глубину стека без моего ведома? Ответ: Да. Измерение представляет собой простой рекурсивный вызов функции, который выполняется за миллисекунды и не производит видимого эффекта. Никаких разрешений не требуется.
Вопрос: Влияет ли глубина стека на нормальное выполнение JavaScript? Ответ: Контролируемая глубина стека применяется только к измеряемому лимиту. Код нормальных веб-приложений редко приближается к лимиту рекурсии. Типичные веб-приложения используют гораздо меньшую глубину стека, чем максимальная.
Вопрос: Контролируется ли глубина стека WASM отдельно?
Ответ: Да. Модули WASM имеют собственную конфигурацию стека. Флаг --bot-stack-seed BotBrowser контролирует поведение стека WASM наряду с поведением стека JavaScript.
Вопрос: Что, если мне нужно запускать глубоко рекурсивные алгоритмы?
Ответ: Контроль глубины стека устанавливает измеряемый лимит, а не искусственное ограничение. Использование --bot-stack-seed=real дает вам нативную глубину стека, если вашему приложению нужна глубокая рекурсия.
Вопрос: Отличается ли глубина стека между безголовым и обычным режимом? Ответ: В стандартном Chromium глубина стека одинакова независимо от режима дисплея. BotBrowser поддерживает эту согласованность с контролируемыми значениями.
Итоги
Глубина стека JavaScript является малоизвестным, но эффективным сигналом отпечатка, который различается между браузерами, платформами и контекстами выполнения. Поскольку она определяется внутренностями движка и выделением стека на уровне ОС, её нельзя контролировать из JavaScript. Флаг --bot-stack-seed BotBrowser обеспечивает прямой контроль лимитов рекурсии на уровне движка, с вариантами соответствия точной глубине профиля, использования нативной глубины или генерации детерминированной вариации из сида. В сочетании с шумовыми и временными сидами контроль глубины стека завершает комплексный подход BotBrowser к детерминированному поведению браузера.
Смежные темы: What is Browser Fingerprinting, Deterministic Browser Behavior, Navigator Property Protection и Performance Timing Fingerprinting.
Похожие статьи
Переведите BotBrowser из исследований в продакшн
Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.