Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in NFC
DescriptionInsufficient policy enforcement in NFC
ComponentNFC
Bug ClassLogic Error
Tracker501749600
Fix commit13ae2a290481 (chromium/src) +73/-25
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
for
services/device/nfc/android/java/src/org/chromium/device/nfc/NfcBlocklist.java
modified
if
services/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.java
  • services/device/nfc/android/junit/src/org/chromium/device/nfc/NfcBlocklistTest.java
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.