Empreinte

Fingerprinting par profondeur de pile : recursion

Comment la profondeur de pile JavaScript et les limites de récursion varient selon le navigateur et la plateforme pour créer des empreintes, et comment contrôler le comportement de la pile.

Documentation

Vous préférez la doc produit maintenue ?

Cet article a une page équivalente dans le centre de documentation. Utilisez les docs pour le flux canonique, les flags à jour et la référence durable.

Introduction

Chaque moteur JavaScript a une limite sur la profondeur de récursion des fonctions. Lorsqu'une fonction s'appelle elle-même trop de fois, le moteur lance une erreur "Maximum call stack size exceeded". Cette limite n'est pas standardisée. Elle varie entre les navigateurs, les versions de navigateur, les systèmes d'exploitation et même entre le thread principal et les Web Workers sur le même navigateur. Les systèmes de pistage exploitent cette variation en mesurant exactement combien d'appels récursifs sont possibles avant le débordement de pile. Le nombre résultant, la profondeur de récursion maximale, devient un signal d'empreinte. Parce que cette valeur dépend des internes de bas niveau du moteur et de l'allocation de pile spécifique à la plateforme, elle est difficile à contrôler depuis JavaScript. Cet article explique comment le fingerprinting par profondeur de pile fonctionne et comment BotBrowser fournit un contrôle précis via le flag --bot-stack-seed.

Impact sur la vie privée

Le fingerprinting par profondeur de pile est l'une des techniques de fingerprinting les plus obscures, mais elle fournit une entropie utile précisément parce que la plupart des outils de confidentialité ne la traitent pas. Les recherches de groupes académiques étudiant le fingerprinting de navigateur ont montré que les limites de récursion peuvent distinguer entre les versions de navigateur, les systèmes d'exploitation et même les builds 32-bit vs. 64-bit du même navigateur.

La technique est efficace parce que :

  • Elle varie entre les navigateurs. Chrome, Firefox et Safari allouent chacun des tailles de pile par défaut différentes, produisant des profondeurs de récursion maximales différentes.
  • Elle varie entre les plateformes. La même version de navigateur sous Windows, macOS et Linux peut rapporter des profondeurs différentes en raison des différences d'allocation de pile au niveau du système d'exploitation.
  • Elle varie entre les contextes. Le thread principal, les workers dédiés, les workers partagés et les modules WASM ont chacun des limites de pile différentes au sein du même navigateur.
  • Elle est stable. Sur la même combinaison navigateur/système d'exploitation/matériel, la limite de récursion est cohérente entre les sessions.

Une étude de l'équipe du navigateur Brave a noté que la profondeur de pile fournit environ 2-3 bits d'entropie.

Contexte technique

Comment fonctionne la profondeur de pile JavaScript

Lorsqu'une fonction JavaScript est appelée, le moteur pousse un nouveau cadre sur la pile d'appels. Chaque cadre contient les variables locales de la fonction, les paramètres et l'adresse de retour. La pile d'appels occupe une région mémoire de taille fixe. Lorsque la pile est pleine, le moteur lance une RangeError.

La profondeur maximale dépend de :

  • L'allocation de taille de pile. Le système d'exploitation alloue une certaine quantité de mémoire pour la pile. Linux utilise par défaut 8 Mo (configurable via ulimit -s), macOS alloue 8 Mo pour le thread principal et 512 Ko pour les threads secondaires, et Windows utilise par défaut 1 Mo.
  • La taille du cadre. Chaque cadre d'appel a une taille différente selon le nombre de variables locales et la comptabilité interne du moteur.
  • Les optimisations du moteur. V8 (Chrome), SpiderMonkey (Firefox) et JavaScriptCore (Safari) implémentent chacun la pile d'appels différemment.
  • Le contexte d'exécution. Le thread principal a typiquement une pile plus grande que les Web Workers.

Mesure de la profondeur de pile

Un script de pistage mesure la profondeur de pile en comptant les appels récursifs jusqu'à ce qu'une exception se produise :

function measureDepth() {
  let depth = 0;
  function recurse() {
    depth++;
    recurse();
  }
  try {
    recurse();
  } catch (e) {
    return depth;
  }
}

Pourquoi les protections existantes échouent

La profondeur de pile est déterminée par la gestion interne de pile du moteur JavaScript. Elle ne peut pas être modifiée depuis JavaScript parce que :

  • L'exception est lancée par le moteur, pas par du code JavaScript
  • La taille de pile est allouée par le système d'exploitation à la création du thread
  • Intercepter la RangeError ne change pas la profondeur à laquelle elle se produit
  • Les extensions de navigateur s'exécutent dans le même moteur et n'ont pas accès à la configuration de pile

L'approche de BotBrowser au niveau du moteur

BotBrowser fournit un contrôle direct sur le comportement de profondeur de pile JavaScript via le flag --bot-stack-seed.

Trois modes de contrôle

Le flag --bot-stack-seed accepte trois types de valeurs :

profile - Correspondre à la profondeur de pile exacte du profil. La limite de récursion correspond à ce que l'appareil profilé produirait. C'est l'option la plus précise pour maintenir une identité d'appareil cohérente.

real - Utiliser la profondeur de pile native. Aucune modification n'est appliquée. La limite de récursion reflète votre configuration matérielle et système d'exploitation réelle.

Graine entière (1-UINT32_MAX) - Générer une variation de profondeur de pile déterministe par session. Chaque graine produit une valeur de profondeur différente mais stable.

Couverture multi-contextes

Le contrôle de profondeur de pile de BotBrowser s'applique à tous les contextes d'exécution JavaScript :

  • Les limites de récursion du thread principal sont contrôlées
  • Les Web Workers (dédiés et partagés) ont leurs profondeurs de pile contrôlées indépendamment mais de manière cohérente avec le profil
  • Le comportement de pile des modules WASM est aussi contrôlé

Configuration et utilisation

Profondeur de pile correspondant au profil

chrome --bot-profile="/path/to/profile.enc" \
       --bot-stack-seed=profile \
       --user-data-dir="$(mktemp -d)"

Variation basée sur la graine

# Profondeur de pile déterministe depuis la graine
chrome --bot-profile="/path/to/profile.enc" \
       --bot-stack-seed=42 \
       --user-data-dir="$(mktemp -d)"

Combiné avec d'autres flags déterministes

chrome --bot-profile="/path/to/profile.enc" \
       --bot-stack-seed=profile \
       --bot-noise-seed=42 \
       --bot-time-seed=42 \
       --user-data-dir="$(mktemp -d)"

Intégration 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}`);

Vérification

Mesure de la profondeur. Exécutez une fonction récursive dans la console du navigateur et enregistrez la profondeur. Comparez entre les sessions avec le même profil et la même graine de pile. La profondeur devrait être identique.

Mesure de la profondeur dans les Workers. Exécutez le même test de récursion dans un Web Worker. La profondeur devrait être différente du thread principal (les workers ont des piles plus petites) mais cohérente entre les sessions.

Vérification inter-machines. Exécutez le même test sur une machine différente avec le même profil et la même graine. La profondeur de pile devrait correspondre.

Bonnes pratiques

  • Utilisez --bot-stack-seed=profile pour une précision maximale. Cela correspond à la profondeur de pile exacte de l'appareil profilé, garantissant la cohérence avec l'agent utilisateur et la plateforme.
  • Combinez avec --bot-noise-seed et --bot-time-seed. La profondeur de pile, le bruit de rendu et le comportement de timing font tous partie de l'empreinte globale du navigateur.
  • Testez dans les contextes de workers. La profondeur de pile varie entre le thread principal et les workers. Vérifiez les deux.
  • Comprenez la portée. La profondeur de pile est un signal parmi beaucoup d'autres. Elle contribue à l'empreinte globale mais n'est pas suffisante seule. Utilisez toujours un profil complet.

FAQ

Q : Combien d'entropie la profondeur de pile fournit-elle ? R : Environ 2-3 bits en soi. La valeur distingue entre les familles de navigateurs, les types de plateformes et parfois des versions spécifiques.

Q : Un site web peut-il mesurer ma profondeur de pile sans que je le sache ? R : Oui. La mesure est un simple appel de fonction récursif qui s'exécute en millisecondes et ne produit aucun effet visible. Aucune permission n'est nécessaire.

Q : La profondeur de pile affecte-t-elle l'exécution normale de JavaScript ? R : La profondeur de pile contrôlée ne s'applique qu'à la limite mesurée. Le code d'application web normal approche rarement la limite de récursion.

Résumé

La profondeur de pile JavaScript est un signal d'empreinte obscur mais efficace qui varie entre les navigateurs, les plateformes et les contextes d'exécution. Parce qu'elle est déterminée par les internes du moteur et l'allocation de pile au niveau du système d'exploitation, elle ne peut pas être contrôlée depuis JavaScript. Le flag --bot-stack-seed de BotBrowser fournit un contrôle direct sur les limites de récursion au niveau du moteur, avec des options pour correspondre à la profondeur exacte d'un profil, utiliser la profondeur native, ou générer une variation déterministe depuis une graine. Combiné avec les graines de bruit et de timing, le contrôle de la profondeur de pile complète l'approche complète de BotBrowser pour le comportement déterministe du navigateur.

Pour les sujets connexes, voir What is Browser Fingerprinting, Deterministic Browser Behavior, Navigator Property Protection, et Performance Timing Fingerprinting.

#Stack-Depth#fingerprinting#Recursion#Javascript#Privacy

Faites passer BotBrowser de la recherche à la production

Utilisez ces guides pour comprendre le modèle, puis passez à la validation multi-plateforme, aux contextes isolés et au déploiement navigateur prêt pour l'échelle.