Chrome · NFC
CVE-2026-17910
Logic Error in NFC
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
forservices/device/nfc/android/java/src/org/chromium/device/nfc/NfcBlocklist.java |
modified | |
ifservices/device/nfc/android/java/src/org/chromium/device/nfc/NfcBlocklist.java |
modified |
Files Changed
services/device/nfc/android/java/src/org/chromium/device/nfc/NfcBlocklist.javaservices/device/nfc/android/junit/src/org/chromium/device/nfc/NfcBlocklistTest.java
Patch
From 13ae2a29048162dbae9867c5d8ef74bbfd922e21 Mon Sep 17 00:00:00 2001
From: Alvin Ji <alvinji@chromium.org>
Date: Wed, 10 Jun 2026 15:38:30 -0700
Subject: [PATCH] nfc: Fix Web NFC Blocklist bypass for YubiKeys
Use suffix matching for YubiKey-5 historical bytes to make the blocklist
robust against capability flag drift caused by firmware updates.
Bug: 501749600
Change-Id: I62b6146f6244beeee5500a9b04b281479fbb1fbc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7916451
Reviewed-by: Fr <beaufort.francois@gmail.com>
Commit-Queue: Alvin Ji <alvinji@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1644931}
---
diff --git a/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcBlocklist.java b/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcBlocklist.java
index f13e54ff..5c43d0d 100644
--- a/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcBlocklist.java
+++ b/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcBlocklist.java
@@ -28,25 +28,16 @@
public class NfcBlocklist {
private static final String TAG = "NfcBlocklist";
- private static final byte[][] STATIC_HISTORICAL_BYTES = {
- new byte[] {
- (byte) 0x80,
- 0x73,
- (byte) 0xc0,
- 0x21,
- (byte) 0xc0,
- 0x57,
- 0x59,
- 0x75,
- 0x62,
- 0x69,
- 0x4b,
- 0x65,
- 0x79
- }, // YubiKey 5 series
- new byte[] {
- (byte) 0x59, 0x75, 0x62, 0x69, 0x6b, 0x65, 0x79, 0x4e, 0x45, 0x4f, 0x72, 0x33
- } // YubiKey NEO
+ // The YubiKey 5 historical bytes end with the proprietary information field:
+ // 0x57 (compact-TLV Tag 5, Length 7) followed by the ASCII bytes for "YubiKey".
+ // We match starting from 0x57 to ignore the preceding card capability flags
+ // (indices 2-4 in the full sequence) which can change with firmware updates.
+ private static final byte[] YUBIKEY_5_HISTORICAL_BYTES_SUFFIX = {
+ 0x57, 0x59, 0x75, 0x62, 0x69, 0x4b, 0x65, 0x79
+ };
+
+ private static final byte[] YUBIKEY_NEO_HISTORICAL_BYTES = {
+ (byte) 0x59, 0x75, 0x62, 0x69, 0x6b, 0x65, 0x79, 0x4e, 0x45, 0x4f, 0x72, 0x33
};
private static final String TRIAL_NAME = "WebNFCBlockList";
@@ -115,8 +106,8 @@
}
/**
- * Returns true if tag is blocked, otherwise false. A tag is blocked if it is part of
- * STATIC_HISTORICAL_BYTES or server provided historical bytes.
+ * Returns true if tag is blocked, otherwise false. A tag is blocked if its historical bytes
+ * match a blocked device (e.g. YubiKeys) or server provided historical bytes.
*
* @see android.nfc.Tag
* @return true if tag is blocked, otherwise false.
@@ -144,15 +135,20 @@
}
/**
- * Returns true if historical bytes are part of STATIC_HISTORICAL_BYTES or server provided
+ * Returns true if historical bytes match a blocked device (e.g. YubiKeys) or server provided
* historical bytes.
*
* @return true if historical bytes are blocked, otherwise false.
*/
@VisibleForTesting
- boolean areHistoricalBytesBlocked(byte[] historicalBytes) {
- for (int i = 0; i < STATIC_HISTORICAL_BYTES.length; i++) {
- if (Arrays.equals(historicalBytes, STATIC_HISTORICAL_BYTES[i])) return true;
+ boolean areHistoricalBytesBlocked(byte @Nullable [] historicalBytes) {
+ if (historicalBytes == null) return false;
+
+ if (endsWith(historicalBytes, YUBIKEY_5_HISTORICAL_BYTES_SUFFIX)) {
+ return true;
+ }
+ if (Arrays.equals(historicalBytes, YUBIKEY_NEO_HISTORICAL_BYTES)) {
+ return true;
}
for (int i = 0; i < mServerProvidedHistoricalBytes.size(); i++) {
if (Arrays.equals(historicalBytes, mServerProvidedHistoricalBytes.get(i))) return true;
@@ -160,6 +156,16 @@
return false;
}
+ private static boolean endsWith(byte[] array, byte[] suffix) {
+ if (array.length < suffix.length) return false;
+ for (int i = 0; i < suffix.length; i++) {
+ if (array[array.length - suffix.length + i] != suffix[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/** Block/unblock NFC tag access for testing use only. */
public void setIsTagBlockedForTesting(Boolean blocked) {
mIsTagBlockedForTesting = blocked;
diff --git a/services/device/nfc/android/junit/src/org/chromium/device/nfc/NfcBlocklistTest.java b/services/device/nfc/android/junit/src/org/chromium/device/nfc/NfcBlocklistTest.java
index aeb7bb2..20c8ac7 100644
--- a/services/device/nfc/android/junit/src/org/chromium/device/nfc/NfcBlocklistTest.java
+++ b/services/device/nfc/android/junit/src/org/chromium/device/nfc/NfcBlocklistTest.java
@@ -80,6 +80,48 @@
assertFalse(areHistoricalBytesBlocked(new byte[] {0x01, 0x02, 0x03}));
}
+ /**
+ * Verifies that the blocklist successfully blocks YubiKey 5 devices even when their ISO 7816-4
+ * card-capability flags change (drift) due to firmware updates. Instead of an exact match, we
+ * check for a suffix match of the proprietary 'YubiKey' identifier string (preceded by the 0x57
+ * TLV tag/length), ignoring the volatile capability flags.
+ *
+ * <p>Hardcoded entry: 80 73 c0 21 c0 57 'YubiKey'
+ *
+ * <p>Variant tested: 80 73 c0 21 c1 57 'YubiKey' (capability byte 4: c0->c1)
+ */
+ @Test
+ @Feature({"NfcBlocklistTest"})
+ public void testYubiKey5CapabilityByteDriftBypassesBlocklist() {
+ NfcBlocklist.overrideNfcBlocklistForTests(/* serverProvidedValues= */ null);
+
+ // Control: the exact 2020 fingerprint is blocked.
+ assertTrue(areHistoricalBytesBlocked(YUBIKEY_5_SERIES_HISTORICAL_BYTES));
+
+ // Same device family, one capability-flag bit flipped (c0 -> c1).
+ byte[] driftedYubiKey5 =
+ new byte[] {
+ (byte) 0x80,
+ 0x73,
+ (byte) 0xc0,
+ 0x21,
+ (byte) 0xc1,
+ 0x57,
+ 0x59,
+ 0x75,
+ 0x62,
+ 0x69,
+ 0x4b,
+ 0x65,
+ 0x79 // "YubiKey"
+ };
+ // Robust blocklist matches the 'YubiKey' issuer-data suffix (including 0x57)
+ // to ignore capability-flag drift.
+ assertTrue(
+ "YubiKey 5 with drifted capability byte should still be blocked",
+ areHistoricalBytesBlocked(driftedYubiKey5));
+ }
+
@Test
@Feature({"NfcBlocklistTest"})
public void testHistoricalBytesWithInvalidProvidedServerValues() {
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page