---
title: "크로미움 148부터 Math.tanh 한 번으로 운영체제 지문이 새는 문제"
published: 2026-07-12T21:12:11.000Z
canonical: https://jeff.news/article/4998
---
# 크로미움 148부터 Math.tanh 한 번으로 운영체제 지문이 새는 문제

크로미움 148 이후 V8이 Math.tanh 계산을 자체 구현 대신 운영체제의 수학 라이브러리에 맡기면서, 같은 입력값도 리눅스·맥·윈도우에서 마지막 비트가 달라지는 지문 신호가 생겼다. 이 차이는 User-Agent로 맥이라고 주장하면서 리눅스 수학 결과를 내는 식의 위장을 바로 들키게 만든다. CSS 삼각함수와 웹 오디오까지 보면 브라우저 수학 경로가 꽤 복잡하게 갈라지고, 제대로 흉내 내려면 실제 운영체제 라이브러리의 비트 단위 동작까지 맞춰야 한다.

- 브라우저 지문 탐지에 또 조용한 신호가 하나 추가됨. 이번엔 캔버스도, 글꼴도, WebGL도 아니고 `Math.tanh`의 마지막 비트임
  - 크로미움 148부터 V8이 `Math.tanh`를 자체 구현 대신 운영체제의 `std::tanh`로 계산하기 시작함
  - 그 결과 리눅스는 `glibc`, 맥은 `libsystem_m`, 윈도우는 `UCRT`의 결과를 그대로 드러냄
  - 겉으로는 같은 크롬이어도 계산 결과의 마지막 비트가 다르면 “너 진짜 그 운영체제 맞아?”를 물어볼 수 있게 됨

- 예시는 꽤 노골적임. 크롬 150에서 `Math.tanh(0.8)`을 돌리면 세 운영체제가 전부 다른 값을 냄
  - 리눅스는 `0.6640367702678491`
  - 맥은 `0.6640367702678490`
  - 윈도우는 `0.6640367702678489`
  - 차이는 고작 1~2 ULP 수준이라 사람 눈엔 거의 안 보이지만, 탐지 코드에겐 충분히 깔끔한 분기값임

> [!IMPORTANT]
> `Math.tanh(0.5)`처럼 모두 같은 값을 내는 입력도 있지만, `Math.tanh(0.8)` 같은 입력 하나만 잘 고르면 리눅스·맥·윈도우를 한 번에 가를 수 있음.

- 이 문제는 꽤 최근에 생긴 회귀에 가까움
  - 크롬 147까지는 V8이 `fdlibm` 기반 번들 구현으로 `tanh`를 계산해서 모든 운영체제에서 같은 비트가 나왔음
  - V8 커밋 `c1486295ae5`에서 `std::tanh`로 바뀌었고, 이 변경이 V8 14.8.57, 즉 크롬 148에 들어감
  - 그래서 크롬 148, 149, 150은 이 신호가 새고, 크롬 147 이하는 이 경로로는 새지 않음

- 핵심은 IEEE 754가 모든 수학 함수를 비트 단위로 똑같이 만들라고 요구하지 않는다는 점임
  - double의 저장 방식은 정해져 있지만 `sin`, `cos`, `tanh`, `exp` 같은 초월함수의 “정확한 반올림”은 강제되지 않음
  - 정확한 반올림은 비싸기 때문에 각 운영체제 라이브러리는 속도와 정확도 사이에서 조금씩 다른 계수, 테이블, 상수로 타협함
  - 보통은 거의 같은 값이 나오지만, 지문 탐지에는 “가끔 갈라지는 입력”만 있으면 충분함

- 더 골치 아픈 건 `Math.*` 전체가 다 새는 게 아니라는 점임
  - V8은 `Math.exp`, `Math.pow`, `Math.atan` 등 대부분의 함수에 번들된 `llvm-libc` 구현을 씀
  - `Math.sin`과 `Math.cos`도 번들된 `glibc` 파생 루틴을 써서 운영체제마다 동일함
  - 예외가 `Math.tanh`라서, 오히려 “다른 Math 함수는 같고 tanh만 운영체제처럼 갈라지는가”라는 비대칭 자체가 체크 포인트가 됨

- CSS 수학 함수는 또 다른 길을 탐. 자바스크립트 `Math.sin`이랑 CSS `sin()`은 같은 함수가 아님
  - Blink의 CSS 삼각함수는 각도를 degree로 줄인 뒤 운영체제의 `std::sin` 같은 함수를 호출함
  - 그래서 CSS의 `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`는 전부 호스트 `libm` 신호를 낼 수 있음
  - 단순히 마지막 함수만 흉내 내면 부족하고, degree 변환과 reduction 단계까지 비트 단위로 맞춰야 함

- 맥은 여기서 한 번 더 함정이 있음. 애플 수학 라이브러리가 하나가 아님
  - Apple Silicon에는 스칼라 `libsystem_m`과 벡터 루틴을 제공하는 `Accelerate` 프레임워크가 같이 있음
  - 둘은 같은 입력에도 다를 수 있고, 기사에 따르면 백만 개 입력 기준 함수에 따라 10~89% 구간에서 결과가 갈라짐
  - 심지어 `cos(0)`도 스칼라 경로는 정확히 `1.0`, Accelerate는 `0.9999999999999999`를 반환하는 식임

- 크롬 안에서도 호출 위치마다 쓰는 라이브러리가 다름. 이게 진짜 귀찮은 부분임
  - 맥 크롬의 `Math.tanh`, CSS 삼각함수, 오디오 컴프레서의 일부 초월함수는 스칼라 `libsystem_m`을 탐
  - Web Audio의 FFT, 벡터 연산, biquad 필터는 맥에서 `Accelerate`를 탐
  - 같은 “맥 수학”이라고 뭉뚱그리면 안 되고, 어떤 코드 경로가 어떤 라이브러리를 부르는지까지 맞춰야 함

```mermaid
sequenceDiagram
    participant 탐지스크립트
    participant 크롬
    participant 브이8
    participant 운영체제수학라이브러리
    participant 탐지서버

    탐지스크립트->>크롬: Math.tanh(0.8) 실행
    크롬->>브이8: 자바스크립트 Math 호출 전달
    브이8->>운영체제수학라이브러리: std::tanh 위임
    운영체제수학라이브러리-->>브이8: 운영체제별 double 결과 반환
    브이8-->>탐지스크립트: 마지막 비트가 다른 값 반환
    탐지스크립트->>탐지서버: User-Agent와 계산 결과 비교
    탐지서버-->>탐지스크립트: 운영체제 위장 여부 판단
```

- “그냥 노이즈 좀 섞으면 되지 않나?”는 여기서 잘 안 통함
  - 실제 운영체제가 내는 값과 안 맞는 랜덤 값은 어느 운영체제와도 일치하지 않아서 바로 튐
  - 호출할 때마다 값이 흔들리면 결정적이어야 할 수학 함수가 비결정적으로 보이는 문제까지 생김
  - 목표는 흐리게 만드는 게 아니라, 주장하는 운영체제의 실제 비트와 정확히 같아지는 것임

> [!WARNING]
> 브라우저 위장 스택에서 수학 결과에 랜덤 노이즈를 넣는 건 방어가 아니라 새 지문을 만드는 쪽에 가까움. 진짜 브라우저는 같은 입력에 매번 같은 값을 내야 함.

- 제대로 막으려면 각 운영체제의 알고리즘을 거의 복원해야 함
  - 대상 `libm`의 minimax 계수, 지수 테이블, reduction 상수까지 뽑아 portable C로 옮겨야 함
  - 타깃이 “틀리게 반올림하는” 입력까지 똑같이 틀려야 비트 단위로 맞음
  - 컴파일러가 FMA를 마음대로 합치거나 풀지 못하게 `-ffp-contract=off` 같은 설정도 필요함

- 윈도우 UCRT는 아예 원본 라이브러리를 리눅스 프로세스에 올려 호출하는 방법도 가능하다고 함
  - UCRT는 x86-64 코드라 리눅스 서버와 같은 ISA에서 실행할 수 있음
  - 대신 윈도우 x64 호출 규약은 System V와 달라서 `ms_abi`를 맞추지 않으면 shadow space 때문에 프레임이 깨질 수 있음
  - 또 UCRT 내부 CPU dispatch 플래그를 FMA 경로로 맞춰야 최신 윈도우 머신과 같은 비트가 나옴

- 성능도 지문이 됨. 비트만 맞추고 느리면 또 들킴
  - 기사에서 첫 빌드는 `fma()`가 소프트웨어 호출로 내려가면서 네이티브보다 2.5~6배 느렸다고 함
  - 탐지자는 `Math.tanh`와 `Math.sin`을 루프로 돌려 비율을 비교할 수 있음
  - 하드웨어 FMA를 켜자 fused op 하나가 명령어 하나로 줄었고, glibc보다 빠르면서도 비트 동일성을 유지했다고 함

- 검증 규모도 꽤 빡셈. 대충 샘플 몇 개 찍어보는 수준이 아님
  - 릴리스마다 87만 1천 개 입력을 돌림
  - dense grid, 구간 경계, subnormal, signed zero, infinity, NaN까지 포함함
  - 실제 맥과 실제 맥 크롬을 디버깅 프로토콜로 돌려 `Math.tanh`와 CSS 삼각함수 결과를 기준값으로 삼음

- 결론은 단순함. 브라우저 수학은 싸게 probing 가능하고, 결정적이고, 위장하기 어렵고, 많은 스푸핑 스택이 놓치는 표면임
  - 방어자 입장에서는 강한 신호가 하나 늘어난 셈임
  - 스크래퍼나 자동화 브라우저 입장에서는 User-Agent, 캔버스, 오디오만 맞춰서는 부족해짐
  - 운영체제 정체성을 주장하려면 그 운영체제의 수학 라이브러리 라우팅과 마지막 비트까지 같이 주장해야 함

---

## 기술 맥락

- 이번 변경의 본질은 V8이 `Math.tanh`를 어디서 계산하느냐예요. 예전엔 엔진 안에 묶인 구현을 써서 운영체제가 달라도 결과가 같았는데, 크로미움 148부터 호스트 `std::tanh`를 부르면서 리눅스, 맥, 윈도우의 `libm` 차이가 그대로 밖으로 나온 거예요.

- 왜 이게 지문이 되냐면, 부동소수점 초월함수는 표준이 모든 마지막 비트까지 강제하지 않기 때문이에요. 각 운영체제는 속도와 정확도 사이에서 다른 계수와 테이블을 쓰고, 그 작은 차이가 `Math.tanh(0.8)` 같은 입력에서 운영체제별 패턴으로 드러나요.

- 구현 관점에서 어려운 부분은 “맥처럼 보이게 하자”가 하나의 함수 교체로 끝나지 않는다는 거예요. 자바스크립트 `Math`, CSS 삼각함수, Web Audio가 서로 다른 코드 경로와 라이브러리를 타기 때문에, 호출 지점별로 어떤 라이브러리의 어떤 동작을 따라야 하는지 먼저 지도처럼 그려야 해요.

- 그래서 노이즈를 넣는 방식은 해결책이 아니에요. 진짜 브라우저의 수학 함수는 결정적이어야 하고, 실제 운영체제 값과 정확히 맞아야 하거든요. 값이 랜덤하게 흔들리거나 어느 운영체제와도 맞지 않으면, 숨기는 게 아니라 더 독특한 지문을 만드는 셈이에요.

- 성능까지 같이 봐야 하는 이유도 여기에 있어요. 비트는 맞는데 `Math.tanh`만 비정상적으로 느리면 탐지 스크립트가 반복 호출로 시간 비율을 재서 잡아낼 수 있어요. 이 기사에서 FMA 설정까지 언급하는 건 정확도와 속도가 둘 다 브라우저 정체성의 일부가 되기 때문이에요.

## 핵심 포인트

- 크로미움 148부터 Math.tanh가 호스트 운영체제의 libm을 타면서 운영체제별 결과 비트가 갈라짐
- Math.tanh(0.8) 같은 입력은 리눅스, 맥, 윈도우 결과가 모두 달라 운영체제 판별 신호가 됨
- V8의 다른 Math 함수 대부분은 번들된 구현을 써서 운영체제 차이가 없고, Math.tanh만 새는 비대칭이 오히려 탐지 포인트가 됨
- CSS 삼각함수와 웹 오디오는 자바스크립트 Math와 다른 코드 경로를 타서 별도의 지문 표면이 됨
- 노이즈를 섞는 방식은 실제 운영체제 값과도 다르고 호출마다 흔들려서 더 쉽게 들킬 수 있음

## 인사이트

브라우저 지문 방어가 User-Agent나 캔버스만 막는다고 끝나는 시대는 진작 지났다는 얘기다. 마지막 1 ULP 차이까지 신호가 되는 영역이라, 자동화 브라우저나 안티봇 회피 스택을 만드는 쪽은 수학 라이브러리 라우팅까지 운영체제 정체성과 맞춰야 한다.
