performance.now() bị cố tình làm thô để chặn tấn công định thời Spectre. Cơ chế này hoạt động ra sao, vì sao cô lập lấy lại độ chính xác, và cái giá phải trả.
Vào tháng 8 năm 2026, Cloudflare công bố một bài viết nhìn lại các cuộc tấn công Spectre trên Workers với một phát hiện đáng lo ngại: trong lúc chỉ thực thi trên CPU, Date.now() và performance.now() bị đóng băng hoàn toàn — không hề có một đồng hồ độ phân giải cao nào chạy liên tục — nhưng chỉ cần một kết nối WebSocket tới máy chủ bên ngoài chuyên phát dấu thời gian là đủ để khôi phục ổn định độ chính xác dưới mili giây. Một lớp phòng thủ cục bộ, bị lách qua bằng một chiếc đồng hồ mượn từ mạng. Các trình duyệt đã đâm phải đúng bức tường này từ nhiều năm trước, vì đúng lý do tương tự, và cũng chưa bao giờ giải quyết được triệt để. Đây là cách performance.now() bị làm mờ, "mờ" cụ thể tương ứng với những con số nào, và vì sao chính chiếc đồng hồ mà JavaScript của bạn đọc được cũng là một tín hiệu có thể dùng để lấy dấu vân tay bạn.
Tóm tắt nhanh
performance.now()bị cố tình làm thô đi, không phải lỗi — đây là biện pháp phòng thủ trước các cuộc tấn công định thời qua cache thuộc lớp Spectre, vốn cần một đồng hồ chính xác mới hoạt động được.- Đặc tả High Resolution Time của W3C định nghĩa một mô hình độ chính xác hai tầng: khoảng 100 micro giây trên một trang thông thường, khoảng 5 micro giây khi trang ở trạng thái cô lập nguồn gốc chéo.
- Cô lập nguồn gốc chéo chính là ngưỡng COOP/COEP mở khóa
SharedArrayBuffer— hai lớp bảo vệ này đi cùng nhau, vì một vùng đệm dùng chung được thăm dò trong vòng lặp liên tục tự nó đã là một chiếc đồng hồ thay thế. - Bản thân hành vi của đồng hồ giờ cũng là một tín hiệu lấy dấu vân tay: bạn nhận được tầng độ chính xác nào, có bao nhiêu độ rung (jitter) được cộng thêm, và một chế độ chống lấy dấu vân tay có đang cố tình làm thô thời gian hơn nữa hay không — tất cả đều khác nhau tùy trình duyệt và cấu hình.
- Chính giới hạn ngăn chặn tấn công cache cũng đặt ra một sàn giới hạn thực sự cho việc đo lường hợp pháp bên trong trình duyệt — phát hiện của Cloudflare trên Workers và
performance.now()bị giới hạn trong trình duyệt là cùng một sự đánh đổi, chỉ khác tầng trong cùng một ngăn xếp công nghệ.
Vì sao trình duyệt lại giới hạn thời gian
performance.now() không phải sinh ra đã mờ. Theo tài liệu tham khảo MDN, mục tiêu thiết kế ban đầu là độ chính xác dưới mili giây: một đồng hồ đơn điệu tăng (monotonic), không bị ảnh hưởng bởi việc chỉnh đồng hồ hệ thống, đủ chính xác để đo thời gian từng lệnh gọi hàm riêng lẻ. Trong vài năm, nó đúng là làm được như vậy.
Rồi Spectre xuất hiện. Các cuộc tấn công dựa trên thực thi suy đoán (speculative execution) hoạt động bằng cách đo thời gian truy cập bộ nhớ: một lần trúng cache (cache hit) chỉ mất vài nano giây, một lần trượt cache (cache miss) mất thêm vài chục nano giây, và khoảng cách nhỏ xíu đó đủ để rò rỉ dữ liệu mà kẻ tấn công vốn không được phép thấy — từng bit một, qua hàng nghìn lần đo. Cuộc tấn công không cần đọc trực tiếp bộ nhớ. Nó chỉ cần một chiếc đồng hồ đủ chính xác để phân biệt trúng và trượt. Tước đi chiếc đồng hồ đó, hoặc làm nó đủ nhiễu, thì đoạn mã trước đây từng rò rỉ dữ liệu sạch sẽ giờ chỉ còn rò rỉ nhiễu.
Đó chính xác là lý do mà đặc tả hr-time-3 nêu thẳng ra. Phần bảo mật của đặc tả gọi đích danh mối lo là "tấn công cache, lấy dấu vân tay thống kê và tấn công vi kiến trúc": một website độc hại có thể đo thời gian của những thao tác trình duyệt hết sức bình thường để tách riêng một người dùng cụ thể ra khỏi đám đông, hoặc moi ra dữ liệu nằm cùng tiến trình mà nó vốn không được trao. Câu trả lời của đặc tả là một thuật toán "làm thô thời gian" được định nghĩa rõ ràng — bất kỳ trình duyệt nào tuân theo đặc tả đều phải giảm độ chính xác của dấu thời gian độ phân giải cao thô, và có thể cộng thêm độ rung cũng như giới hạn tần suất gọi liên tiếp. Các hãng trình duyệt đã triển khai điều này chỉ trong vài tuần sau khi Spectre được công bố năm 2018, chỉ sau một đêm đã làm thô performance.now() từ những bước nhảy dưới micro giây lên thành hàng chục, thậm chí hàng trăm micro giây — vài engine còn lùi hẳn về trọn một mili giây — và nó vẫn bị giới hạn như vậy kể từ đó.
Mô hình hai tầng: cô lập nguồn gốc chéo mua lại được gì
Giới hạn mọi trang mãi mãi ở cùng một mức độ chính xác thô sẽ phá vỡ những trường hợp sử dụng đo lường thực sự cần thiết — xử lý âm thanh bằng WebAssembly, codec video, và tính toán khoa học trong trình duyệt đều dựa vào performance.now() để có được thời gian thực sự hữu dụng. Giải pháp thỏa hiệp của đặc tả là một tầng thứ hai, tinh vi hơn, chỉ mở khóa cho những trang chủ động chọn cô lập.
Một tài liệu trở thành cô lập nguồn gốc chéo bằng cách trả về hai tiêu đề phản hồi:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cặp tiêu đề này cắt đứt mối liên hệ của trang với các cửa sổ mở nguồn gốc chéo và popup, đồng thời giới hạn những tài nguyên nguồn gốc chéo mà nó có thể nhúng nếu không có sự đồng ý rõ ràng. Đổi lại, window.crossOriginIsolated chuyển thành true, và theo đúng các mức độ chính xác mà chính thuật toán làm thô thời gian quy định, giới hạn này sẽ được nới lỏng: khoảng 100 micro giây độ chính xác trên trang thông thường, khoảng 5 micro giây khi cô lập được kích hoạt. Không con số nào trong hai con số này là hằng số cứng, phổ quát — đặc tả diễn đạt cả hai như một độ chính xác tối thiểu "hoặc cao hơn", nên từng engine trình duyệt hoàn toàn có thể tự giới hạn chặt hơn, và trên thực tế thường làm vậy.
Cùng một cờ cô lập đó cũng khóa/mở SharedArrayBuffer. Đây không phải là sự trùng hợp được gộp vào cho tiện: một vùng đệm dùng chung, có thể thay đổi, được hai luồng thăm dò trong vòng lặp liên tục tự nó đã là một nguyên thủy định thời — các nhà nghiên cứu Spectre từng chứng minh có thể dùng nó để dựng lại một đồng hồ độ phân giải cao ngay cả sau khi performance.now() đã bị giới hạn. Cô lập một trang đóng cả hai cánh cửa cùng lúc — chiếc đồng hồ trực tiếp và chiếc đồng hồ tự chế này — đó là lý do vì sao chúng được kiểm soát bởi cùng một cặp tiêu đề, thay vì hai lựa chọn bật/tắt riêng biệt.
Date.now() nằm hoàn toàn ngoài hệ thống này. Nó vốn đã bị giới hạn ở độ chính xác một mili giây từ trước khi tất cả chuyện này xảy ra, bám theo giờ hệ thống (wall-clock) chứ không phải một bộ đếm đơn điệu, và cũng không chính xác hơn khi cô lập — cả thuật toán làm thô thời gian lẫn phần thưởng độ chính xác từ cô lập đều chỉ áp dụng riêng cho các đồng hồ độ phân giải cao, không liên quan đến đồng hồ Date gốc.
Góc độ lấy dấu vân tay: bản thân chiếc đồng hồ là một tín hiệu
Một khi độ chính xác phụ thuộc vào trạng thái cô lập, engine trình duyệt và cấu hình, chiếc đồng hồ không còn là hạ tầng trung lập nữa mà trở thành một thứ nữa mà trang có thể đọc được về bạn. Một đoạn script có thể đo độ rung performance.now() của chính nó — gọi nó dồn dập trong một vòng lặp liên tục và nhìn vào khoảng cách giữa các lần đọc liền nhau — từ đó suy ra nó đang nhận tầng độ chính xác nào, gợi ý liệu trang có đang thực sự cô lập như nó mong đợi hay không, và kiểu làm tròn đặc trưng của engine trình duyệt nào đã tạo ra mẫu hình đó.
Một số trình duyệt còn đi xa hơn một cách có chủ đích. Tùy chọn privacy.resistFingerprinting của Firefox — luôn bật trong Tor Browser — cố tình làm tròn đồng hồ đến các bước thô hơn, chồng lên trên lớp phòng thủ Spectre cơ bản theo đặc tả, dùng đúng triết lý "đồng nhất hóa" mà chúng tôi đã phân tích chi tiết về RFP nói chung: khiến mọi trình duyệt được bảo vệ đều báo cáo cùng những giá trị thô cứng như nhau, để độ rung đồng hồ của bất kỳ thực thể cụ thể nào cũng không nổi bật giữa đám đông. Một trang lấy dấu vân tay dựa trên nhiễu định thời dưới mili giây sẽ thu được tín hiệu ít hơn hẳn từ một trình duyệt bật RFP so với một trình duyệt thông thường — và bản thân sự "ít tín hiệu hơn" đó cũng là một khác biệt có thể phát hiện được.
Đây cũng chính là lãnh địa mà hướng dẫn đầy đủ về lấy dấu vân tay trình duyệt của chúng tôi đã đề cập: một tín hiệu không cần phải là định danh duy nhất mới có ích. Độ chính xác và độ rung của đồng hồ tự thân mang entropy thấp — chúng chủ yếu chỉ nói lên "đây là Chromium" hay "đây là Firefox bật RFP" chứ không tách riêng được một khách truy cập cụ thể khỏi đám đông — nhưng khi cộng dồn vào hàng chục tín hiệu khác, đó chính xác là kiểu đóng góp mà bài viết của chúng tôi về entropy dấu vân tay và tập ẩn danh đo lường: nhỏ khi xét riêng lẻ, khác không khi cộng dồn.
Nếu bạn muốn tận mắt thấy chồng tín hiệu đó thay vì chỉ đọc về nó, kiểm tra dấu vân tay của BrowserInsight chạy hoàn toàn ngay trong trình duyệt của bạn và liệt kê song song những thuộc tính mà một trang web có thể đọc được từ bạn — canvas, WebGL, phông chữ, các gợi ý phần cứng. Đó là cách trung thực để đánh giá xem một tín hiệu entropy thấp như hành vi của đồng hồ thực sự đóng góp bao nhiêu vào hồ sơ của chính bạn.
Góc độ phát hiện: định thời bắt được thứ mà giả lập không làm giả nổi
Dù có bị giới hạn hay không, performance.now() vẫn đủ chính xác để bắt được một điều hoàn toàn khác: một tác vụ trông như đã hoàn thành nhưng thực tế lại không diễn ra đúng như nó tự nhận. Kết xuất GPU thật, biên dịch JIT thật, và giải mã phần cứng thật đều mang một chi phí đặc trưng — không phải một phép đo đơn lẻ, mà là một phân phối thời lượng qua nhiều lần lặp lại thao tác, được định hình bởi silicon thật vừa làm nóng cache vừa lập lịch công việc trên các lõi thật. Một bộ dựng hình bằng phần mềm giả làm GPU, hoặc một trình duyệt chạy bên trong môi trường bị chèn công cụ theo dõi nặng nề, thường có thể tái tạo kết quả của công việc đó một cách thuyết phục nhưng lại khó tái tạo được phân phối thời gian của nó — quá đều, quá nhanh, hoặc quá chậm theo cách không khớp với bất kỳ thiết bị thật nào.
Đây là một trong những phép so sánh nguồn đáng tin và không đáng tin mà mô hình phát hiện nói dối của CreepJS dựa vào: chạy cùng một phép tính trên luồng chính và bên trong một Worker, hoặc so sánh một khả năng được tự nhận với chữ ký định thời mà khả năng đó lẽ ra phải tạo ra, rồi đánh dấu những trường hợp không khớp. Đây cũng là một phần lý do vì sao trình duyệt headless và tự động hóa vẫn tiếp tục bị bắt ngay cả sau khi những dấu hiệu lộ liễu — cờ navigator.webdriver bị thiếu, User-Agent đáng ngờ — đã được vá. Giả mạo hình dạng của một giá trị là điều làm được. Giả mạo phân phối thời gian tạo ra giá trị đó dưới một chiếc đồng hồ đã bị làm thô và cộng thêm độ rung, thì lại là mục tiêu khó hơn nhiều — chính vì chiếc đồng hồ đang bắt lỗi cũng là chiếc đồng hồ thô và ồn mà các biện pháp giảm nhẹ Spectre để lại.
Cái giá phải trả: độ chính xác không lấy lại được
Phát hiện của Cloudflare trên Workers và giới hạn performance.now() trong trình duyệt là cùng một sự đánh đổi, chỉ ở những điểm khác nhau trong ngăn xếp công nghệ: đóng băng hoặc làm thô đồng hồ, đóng một kênh bên (side channel), và chấp nhận rằng việc đo lường hợp pháp sẽ trở nên nhiễu hơn như một tác dụng phụ. Các nhà nghiên cứu của Cloudflare đã chứng minh rằng một chuyến khứ hồi qua mạng tới đồng hồ bên ngoài có thể khôi phục độ chính xác mà kẻ tấn công muốn lấy lại. Một trang chạy trong trình duyệt của bạn không có lối thoát tương tự cho các phép đo nội bộ của chính nó — mọi khoảng thời gian mà một script đo bằng performance.now(), kể cả những phép đo đứng sau các công cụ chẩn đoán mạng trong trình duyệt, đều thừa hưởng sàn giới hạn "làm thô thời gian" này cùng mọi độ rung chồng lên trên đó.
Sàn giới hạn đó nhỏ về giá trị tuyệt đối — khoảng 100 micro giây ngoài cô lập, gần 5 micro giây bên trong — gần như không đáng kể với bất cứ thứ gì được đo bằng đơn vị giây trọn vẹn. Nhưng nó vẫn là thêm một nguồn nhiễu giữa các lần chạy, chồng lên sự biến thiên vốn đã có sẵn khi đo một tuyến đường mạng thực, điều mà bài viết của chúng tôi về vì sao kết quả kiểm tra tốc độ luôn khác nhau đã phân tích kỹ từ phía mạng. Đồng hồ không phải là nguồn nhiễu chủ đạo ở đây. Nó chỉ đơn giản là chưa bao giờ hoàn toàn im lặng, theo đúng thiết kế — cùng một thiết kế khiến một trang không thể đọc cache của bạn từng bit một.
Câu hỏi thường gặp
Vì sao trình duyệt lại làm performance.now() kém chính xác hơn?
Để đóng một kênh bên thuộc lớp Spectre. Các cuộc tấn công định thời qua cache cần một chiếc đồng hồ đủ chính xác để phân biệt trúng cache và trượt cache — chênh lệch chỉ vài nano giây. Làm thô đồng hồ và cộng thêm độ rung, theo yêu cầu của thuật toán "làm thô thời gian" trong đặc tả W3C, khiến sự phân biệt đó trở nên không đáng tin cậy mà không làm hỏng đồng hồ đối với việc sử dụng thông thường.
Điều này có nghĩa là tôi không thể đo hiệu năng chính xác trong trình duyệt nữa?
Không, đối với bất cứ điều gì ở quy mô thời gian con người có thể cảm nhận. Giới hạn này khiến bạn mất khoảng 100 micro giây độ chính xác ngoài cô lập, hoặc khoảng 5 micro giây bên trong — không đáng kể khi đo một yêu cầu mạng hay một lượt kết xuất, nhưng là thật nếu bạn đang cố đo từng lần truy cập bộ nhớ riêng lẻ — và đó chính xác là mục đích của giới hạn này.
Làm sao để biết một trang có đang cô lập nguồn gốc chéo hay không?
Kiểm tra window.crossOriginIsolated trong console — giá trị này chỉ là true khi trang trả về cả tiêu đề Cross-Origin-Opener-Policy: same-origin lẫn tiêu đề Cross-Origin-Embedder-Policy, theo MDN. Hầu hết các trang thông thường không làm điều này, vì cô lập giới hạn những nội dung nguồn gốc chéo mà chúng có thể nhúng.
resistFingerprinting của Firefox có làm thay đổi độ chính xác định thời hơn nữa không?
Có. Chồng lên trên lớp giới hạn cơ bản do Spectre thúc đẩy mà mọi trình duyệt đều áp dụng, RFP làm tròn đồng hồ đến các bước thô hơn, đồng nhất hơn, đúng để việc lấy dấu vân tay dựa trên độ rung đồng hồ thu được ít tín hiệu hơn — cùng cách tiếp cận đồng nhất hóa mà chúng tôi phân tích chi tiết trong bài về RFP.
Đề xuất đọc thêm
- Hướng dẫn đầy đủ về lấy dấu vân tay trình duyệt: cách bảo vệ quyền riêng tư
- resistFingerprinting chi tiết: Firefox và Tor Browser
- CreepJS chi tiết: phát hiện nói dối trong dấu vân tay bắt được giả mạo ra sao
- Trình duyệt headless bị phát hiện: Selenium, Playwright và CDP
- Entropy dấu vân tay và tập ẩn danh


