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 <[email protected]> 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 <[email protected]> Commit-Queue: Alvin Ji <[email protected]> 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