Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 33 additions & 24 deletions install/hardware/apple/fix-brcmfmac-supplicant.sh
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
# Apple Macs ship Broadcom Wi-Fi driven by brcmfmac, whose firmware runs the WPA
# handshake itself. On these parts that offload fails against an access point in
# WPA2/WPA3 transition mode: the client associates, the four-way handshake never
# completes, and NetworkManager reports the password as wrong.
# Apple Macs ship Broadcom Wi-Fi driven by brcmfmac. On legacy pre-T2 parts
# (such as BCM43602 and BCM4350), the 2015-era firmware offload fails against
# access points in WPA2/WPA3 transition mode: the client associates, the
# four-way handshake never completes, and NetworkManager reports the password
# as wrong. wpa_supplicant 2.11 made it worse on every brcmfmac part by
# completing WPA state from the driver's authorization event, which broke
# WPA2-PSK and WPA3-SAE association outright. That change (hostap 41638606054a)
# is still in hostap 2.12 and main; what clears it here is Arch's downstream
# revert of it, 0008-Revert-Mark-authorization-completed-on-driver-indica.patch
# at the root of the wpa_supplicant packaging directory, taken from Fedora and
# first shipped in wpa_supplicant 2:2.12-1.
# A wpa_supplicant release that drops that patch brings the breakage back, and
# the T2 group below is where to notice it.
#
# feature_disable turns off the firmware supplicant (FWSUP, 0x2000) and firmware
# authenticator (FWAUTH, 0x80000), handing the handshake back to wpa_supplicant
# in software.
# Disabling the firmware supplicant (FWSUP, 0x2000) and SAE (0x80000) hands the
# WPA2 handshake back to wpa_supplicant in software.
#
# This quirk already shipped for T2 Macs, detected by the T2 PCI ID that every
# one of them carries. The bug is in the Broadcom firmware, not in the T2 bridge,
# so every Mac whose Wi-Fi brcmfmac drives needs it: a MacBookPro11,4 with
# BCM43602 and 2015 firmware fails exactly this way, and connects once the
# offload is disabled.
# This workaround MUST NOT be applied to modern Broadcom chips, which
# offload both the handshake and SAE and so lose WPA3-only support when it is
# applied:
# 1. BCM4364 (14e4:4464, in the T2 Macs and iMac19,x): disabling SAE offload
# prevents connecting to WPA3-only networks (#9802).
# 2. Apple Silicon (14e4:4425 / 4433): brcmfmac wedges completely with this
# quirk (#7439).
#
# The IDs are brcmfmac's own, from brcm_hw_ids.h: BCM43602 and its single-band
# variants in 2015-2017 Macs, BCM4350, BCM4355 and BCM4364 in the 2018-2019
# machines including the T2-less iMac19,1 and iMac19,2, and BCM4377/4378/4387
# from the T2 era on. The BCM4360 in 2013-2015 Macs is deliberately absent: it
# runs the out-of-tree wl driver, which never reads a brcmfmac option.
# Therefore, only apply to legacy pre-T2 Broadcom hardware with known-affected parts:
# BCM43602 (43ba, 43bb, 43bc) and BCM4350 (43a3). BCM4355 (43dc) and BCM4377
# (4488) are deliberately absent: Linux documents both only on T2 Macs, which
# the T2 check below already excludes.
sys_vendor="$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null || true)"

if lspci -nn | grep "106b:180[12]" >/dev/null ||
{ [[ $sys_vendor == Apple* ]] &&
lspci -nn | grep -E "14e4:(43ba|43bb|43bc|43a3|43dc|4464|4488|4425|4433)" >/dev/null; }; then
echo "Detected a Mac with Broadcom Wi-Fi; running the WPA handshake in software"
if [[ $sys_vendor == Apple* ]] &&
! lspci -nn | grep "106b:180[12]" >/dev/null &&
lspci -nn | grep -E "14e4:(43ba|43bb|43bc|43a3)" >/dev/null; then
echo "Detected a legacy Mac with Broadcom Wi-Fi; running the WPA handshake in software"

mkdir -p /etc/modprobe.d
cat > /etc/modprobe.d/brcmfmac.conf <<'EOF'
# Broadcom's firmware supplicant and authenticator fail the WPA four-way
# handshake on Apple hardware, which surfaces as a rejected password. Disable
# both so wpa_supplicant performs the handshake instead.
# Broadcom firmware on pre-T2 Macs fails the WPA four-way handshake on
# transition-mode networks. Disable firmware supplication and SAE so
# transition-mode networks use the software WPA2 handshake instead.
options brcmfmac feature_disable=0x82000
EOF
fi
11 changes: 6 additions & 5 deletions migrations/1786391100.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ conf="${OMARCHY_BRCMFMAC_CONF:-/etc/modprobe.d/brcmfmac.conf}"

sys_vendor="$(cat "$dmi_vendor" 2>/dev/null || true)"

if ! lspci -nn | grep "106b:180[12]" >/dev/null &&
if lspci -nn | grep "106b:180[12]" >/dev/null ||
! { [[ $sys_vendor == Apple* ]] &&
lspci -nn | grep -E "14e4:(43ba|43bb|43bc|43a3|43dc|4464|4488|4425|4433)" >/dev/null; }; then
lspci -nn | grep -E "14e4:(43ba|43bb|43bc|43a3)" >/dev/null; }; then
exit 0
fi

# T2 installs already carry this from the installer, so the common case is a
# no-op for the first user and every user after them. Only an active options
# line counts: someone who commented theirs out still needs this.
# Only an active options line counts: someone who commented theirs out still
# needs this. T2 installs no longer get the quirk from the installer; on the
# pre-T2 Macs this migration targets, the installer covers new installs and
# this covers existing ones.
if [[ -f $conf ]] &&
grep -Eq '^[[:space:]]*options[[:space:]]+brcmfmac[[:space:]].*feature_disable=0x82000' "$conf"; then
exit 0
Expand Down
88 changes: 88 additions & 0 deletions migrations/1790165000.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
echo "Restore WPA3 support on Broadcom BCM4364 and Apple Silicon Macs"

# Broadcom BCM4364 and Apple Silicon (BCM4378/4387) carry modern firmware and
# must not have SAE disabled by Omarchy's 0x82000 transition-mode quirk. The
# T2 Macs that never carried BCM4364 -- MacBook Air 13" Late 2018 / True Tone
# 2019 (BCM4355, 14e4:43dc) and MacBook Pro 13" Touch 2019 / 2020 and MacBook
# Air 13" Scissor 2020 (BCM4377, 14e4:4488) -- are excluded as a group, since
# the quirk is a wpa_supplicant workaround rather than a firmware one. Remove
# only the exact blocks written by the old T2 installer and the Broadcom
# supplicant setup, wherever they sit in the file. Administrator-authored
# variants remain untouched.
conf="${OMARCHY_BRCMFMAC_CONF:-/etc/modprobe.d/brcmfmac.conf}"

# Only run cleanup on Macs that carried the unneeded quirk:
# 1. T2 Macs (106b:180[12])
# 2. BCM4364 Macs (14e4:4464)
# 3. Apple Silicon Macs (14e4:(4425|4433))
if ! lspci -nn | grep -E "106b:180[12]|14e4:(4464|4425|4433)" >/dev/null; then
exit 0
fi

[[ -f $conf ]] || exit 0

# Read through sudo so a root-only file fails the migration and gets retried
# rather than reading as empty and burning the per-user migration marker.
content="$(sudo cat "$conf")"

legacy_block='# Fix for T2 MacBook WiFi connectivity issues
options brcmfmac feature_disable=0x82000'

current_block="# Broadcom's firmware supplicant and authenticator fail the WPA four-way
# handshake on Apple hardware, which surfaces as a rejected password. Disable
# both so wpa_supplicant performs the handshake instead.
options brcmfmac feature_disable=0x82000"

# Drop either owned block wherever it sits on whole-line boundaries, copying
# every other line through untouched. Matching line sequences literally (rather
# than as a shell pattern) keeps a reworded comment or an administrator-merged
# options line out of scope, and lets a block sandwiched between two user lines
# go the same way as a trailing one.
rest="$(printf '%s\n' "$content" | awk -v legacy="$legacy_block" -v current="$current_block" '
BEGIN {
legacy_count = split(legacy, legacy_line, "\n")
current_count = split(current, current_line, "\n")
}
{ line[NR] = $0 }
END {
i = 1
while (i <= NR) {
if (owned(i, legacy_count, legacy_line)) count = legacy_count
else if (owned(i, current_count, current_line)) count = current_count
else count = 0

if (count > 0) {
for (j = 0; j < count; j++) owned_line[i + j] = 1
i += count
} else {
i++
}
}

for (k = 1; k <= NR; k++) if (!(k in owned_line)) print line[k]
}
function owned(start, count, text, j) {
if (start + count - 1 > NR) return 0
for (j = 0; j < count; j++) if (line[start + j] != text[j + 1]) return 0
return 1
}
')"

# Both sides have their trailing newlines stripped by command substitution, so
# an untouched file compares equal to itself. Anything else means an owned block
# was removed.
[[ $rest != "$content" ]] || exit 0

# Request the reboot before editing because brcmfmac reads module options only
# when it loads. Do not reload it during an update carried over Wi-Fi.
omarchy-state set reboot-required

if [[ -z $rest ]]; then
if [[ -L $conf ]]; then
: | sudo tee "$conf" >/dev/null
else
sudo rm -f "$conf"
fi
else
printf '%s\n' "$rest" | sudo tee "$conf" >/dev/null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Direct rewrite risks settings loss

When this migration removes an Omarchy block from a file that also contains administrator settings, tee truncates the file before writing the remaining content. If the update is interrupted or the write fails, those settings can be lost or left incomplete. A later retry may no longer find a complete block to remove and can mark the migration finished. Write the replacement completely before changing the original, while retaining the tested symlink behavior.

Fix in Claude Code Fix in Codex Fix in Cursor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unrelated trailing bytes change

When an owned block is removed, command substitution strips trailing newlines from the retained content, and this printf writes it back with one final newline. A file with trailing blank lines or a surviving last line without a newline therefore changes outside the owned block. This breaks the migration’s byte-preservation promise and makes harmless administrator edits harder to distinguish from cleanup; preserve the original line endings and test the retained content byte-for-byte.

Fix in Claude Code Fix in Codex Fix in Cursor

fi
83 changes: 63 additions & 20 deletions test/shell.d/brcmfmac-supplicant-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,40 @@ run_leaf() {
bash -eE -o pipefail -c 'source "$1"' bash "$script" </dev/null
}

# The quirk shipped for T2 Macs first, and the move to its own leaf must not
# drop them.
run_leaf "Apple Inc." 4488 1 >/dev/null
grep -q 'feature_disable=0x82000' "$conf" 2>/dev/null ||
fail "a T2 Mac still gets the quirk" "$(ls -R "$test_tmp/etc" 2>&1)"
pass "a T2 Mac still gets the quirk"

# Every Broadcom part brcmfmac drives, on a Mac with no T2 to detect: BCM43602
# and its single-band variants, BCM4350, BCM4355, BCM4364, BCM4378, BCM4387.
for wifi_id in 43ba 43bb 43bc 43a3 43dc 4464 4425 4433; do
# A T2 Mac carries BCM4364 with modern firmware and must NOT get the quirk.
run_leaf "Apple Inc." 4464 1 >/dev/null
[[ ! -f $conf ]] || fail "a T2 Mac does not get the quirk" "$(ls -R "$test_tmp/etc" 2>&1)"
pass "a T2 Mac does not get the quirk"

# Not every T2 Mac carries BCM4364. BCM4355 (43dc) and BCM4377 (4488) are
# documented by Linux only on T2 boards, and neither is in the legacy list, so
# both gates already exclude these.
for wifi_id in 43dc 4488; do
run_leaf "Apple Inc." "$wifi_id" 1 >/dev/null
[[ ! -f $conf ]] || fail "a T2 Mac with 14e4:$wifi_id does not get the quirk" "$(ls -R "$test_tmp/etc" 2>&1)"
done
pass "T2 Macs on BCM4355 and BCM4377 do not get the quirk"

# No Mac carries both a T2 and a legacy part, so the T2 check is a second gate
# behind the ID list and no documented hardware exercises it on its own. Pin it
# anyway, or deleting it leaves every case here still passing.
run_leaf "Apple Inc." 43ba 1 >/dev/null
[[ ! -f $conf ]] || fail "a T2 Mac reporting a legacy part does not get the quirk" "$(ls -R "$test_tmp/etc" 2>&1)"
pass "the T2 check alone keeps the quirk off a T2 Mac on a legacy part"

# Modern BCM4364 (non-T2) and Apple Silicon (BCM4378/4387) also must not get the quirk.
for wifi_id in 4464 4425 4433; do
run_leaf "Apple Inc." "$wifi_id" 0 >/dev/null
[[ ! -f $conf ]] || fail "modern brcmfmac part gets no quirk" "14e4:$wifi_id"
done
pass "modern BCM4364 and Apple Silicon get no quirk"

# Legacy pre-T2 parts: BCM43602 and its single-band variants, BCM4350.
for wifi_id in 43ba 43bb 43bc 43a3; do
run_leaf "Apple Inc." "$wifi_id" 0 >/dev/null
[[ -f $conf ]] || fail "a Mac without a T2 gets the quirk" "14e4:$wifi_id"
[[ -f $conf ]] || fail "legacy Mac without a T2 gets the quirk" "14e4:$wifi_id"
done
pass "every brcmfmac part on a Mac without a T2 gets the quirk"
pass "legacy brcmfmac parts on pre-T2 Macs get the quirk"

# Older Macs report the vendor differently.
run_leaf "Apple Computer, Inc." 43ba 0 >/dev/null
Expand Down Expand Up @@ -130,19 +150,42 @@ run_migration() {
bash -euo pipefail "$migration" >/dev/null
}

# A T2 install from before the quirk shipped has no config at all, so this is
# the case that proves the T2 gate itself still fires -- and it is the piped
# grep, run under pipefail, that #6608 was about.
# A T2 install or modern BCM4364 Mac is skipped by the legacy migration.
rm -rf "$test_tmp/etc"
run_migration "Apple Inc." 4488 1
run_migration "Apple Inc." 4464 1
[[ ! -e $conf ]] || fail "the legacy migration skips T2 Macs" "$(cat "$conf")"
[[ ! -s $calls ]] || fail "the legacy migration escalates nothing on T2 Macs" "$(cat "$calls")"
pass "the legacy migration skips T2 Macs"

run_migration "Apple Inc." 4464 0
[[ ! -e $conf ]] || fail "the legacy migration skips BCM4364 Macs" "$(cat "$conf")"
pass "the legacy migration skips BCM4364 Macs"

# BCM4355 (43dc) and BCM4377 (4488) are not in the legacy list either, so both
# gates already skip these.
for wifi_id in 43dc 4488; do
run_migration "Apple Inc." "$wifi_id" 1
[[ ! -e $conf ]] || fail "the legacy migration skips a T2 Mac with 14e4:$wifi_id" "$(cat "$conf")"
done
pass "the legacy migration skips T2 Macs on BCM4355 and BCM4377"

# The T2 check on its own, for the same reason as in the leaf above: without a
# case here, deleting it from the migration leaves this suite passing.
run_migration "Apple Inc." 43ba 1
[[ ! -e $conf ]] || fail "the legacy migration skips a T2 Mac on a legacy part" "$(cat "$conf")"
[[ ! -s $calls ]] || fail "the legacy migration escalates nothing there" "$(cat "$calls")"
pass "the T2 check alone stops the migration on a T2 Mac with a legacy part"

# Legacy pre-T2 Macs that predate the quirk get fixed.
rm -rf "$test_tmp/etc"
run_migration "Apple Inc." 43ba 0
grep -q '^options brcmfmac feature_disable=0x82000$' "$conf" 2>/dev/null ||
fail "the migration fixes a T2 install that never got the quirk" "$(ls -R "$test_tmp/etc" 2>&1)"
# The option only reaches the driver when brcmfmac next loads.
fail "the migration fixes an install on a legacy pre-T2 Mac" "$(ls -R "$test_tmp/etc" 2>&1)"
grep -Fq $'omarchy-state\tset\treboot-required' "$calls" ||
fail "the migration asks for the reboot that applies it" "$(cat "$calls")"
pass "the migration fixes a T2 install that never got the quirk"
pass "the migration fixes an install on a legacy pre-T2 Mac"

run_migration "Apple Inc." 4488 1
run_migration "Apple Inc." 43ba 0
(( $(grep -c '^options brcmfmac feature_disable=0x82000$' "$conf") == 1 )) ||
fail "the migration is idempotent" "$(cat "$conf")"
[[ ! -s $calls ]] || fail "a repaired install is left untouched" "$(cat "$calls")"
Expand Down
Loading