8276 Commits
Author SHA1 Message Date
3dcdb353f2 [NFC] Fix MIFARE Plus 2K SL1 transit parsers (#1037) (#1038)
* [NFC] Fix MIFARE Plus 2K SL1 transit parsers after MfClassicType2k

After #1016 promoted genuine MIFARE Plus S/X 2K cards in SL1 (SAK 0x08 +
Plus ATS) from MfClassicType1k to the new MfClassicType2k, the supported-
card parsers for those cards stopped working:

- Type gates (get_card_config / extract_purse_data) knew only 1K/4K, so
  parse() rejected 2K -- plantain, troika.
- mf_classic_is_card_read() now needs all 32 sectors / 64 keys for a 2K
  card, so a targeted read() that keys only its own sectors returns
  MfClassicErrorPartialRead -> is_read = false -> the app diverts to the
  dict-attack scene and never reaches parse() -- sevppk, szppk_so,
  two_cities (and the above).

Handle MfClassicType2k explicitly as the Classic 1K these cards present
(same lower-sector keys / data sector), and accept a partial read in the
plugins' read() so parse() runs and validates the key.

social_moscow / kazan (plain Classic) and sk_tk (4K) are not promoted to
2K and are left unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(changelog): note Plus 2K SL1 transit parser fix (PR #1038)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 22:28:18 +03:00
MX 3b160a5e0e bump apps tag 2026-07-15 05:47:36 +03:00
MX f629d2cb98 bump apps tag 2026-07-13 15:08:22 +03:00
MX d71868364f disable bot completely due to endless email spam [ci skip]
todo: disable comments for old PRs and issues, keep them only for new ones
2026-07-13 13:36:45 +03:00
MX 9bcabc0134 bump apps tag and sync api 2026-07-11 16:00:44 +03:00
MX 17ad4461a4 Merge remote-tracking branch 'OFW/dev' into dev [ci skip] 2026-07-09 13:19:29 +03:00
7432d21a7e CCID: move USB layer from firmware HAL into ccid_test app (#4407)
* CCID: move USB layer from firmware HAL into ccid_test app

The CCID USB device interface no longer needs to live in the firmware.
A FAP can define its own FuriHalUsbInterface and drive the libusb_stm32
device stack directly: the usbd_* helpers are inline static in
usbd_core.h and dispatch through the dev->driver vtable, and those
headers already ship in the app SDK, so no new symbols must be exported.

- Move furi_hal_usb_ccid.{c,h} into applications/debug/ccid_test as
  ccid_usb.{c,h} (interface renamed usb_ccid -> ccid_usb_interface,
  furi_hal_usb_ccid_* -> ccid_usb_*). String-descriptor indices are
  hardcoded since furi_hal_usb_i.h is not part of the SDK.
- Delete the firmware CCID module and its extern/include hooks.
- Drop the 4 CCID entries from f7 + f18 api_symbols.csv (keeping the
  libusb usb_ccid.h header), bump API 87.3 -> 88.0 (breaking).

Frees ~1.7 KB of base-firmware flash; CCID code now only costs space
in the FAP while the app runs. Verified on hardware: enumerates as a
CCID reader (076b:3a21, class 0x0b) and passes the app's APDU tests.

* apply fbt format

---------

Co-authored-by: Štefan Croitoru <66339601+turbospok@users.noreply.github.com>
2026-07-09 13:16:57 +03:00
MX 48b6484ce2 bump apps tag 2026-07-09 10:51:43 +03:00
Mykhailo ShevchukandClaude Opus 4.8 568c456f52 [CI] Add lock-threads workflow (lock closed issues/PRs after 14d idle) [ci skip]
Daily dessant/lock-threads@v6 sweep + manual dispatch. Locks only closed
threads that have been inactive for 14 days; never closes anything and open
threads are untouched. Leaves a comment pointing users to open a fresh issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:50:30 +03:00
Elly RomanovaandGitHub d1019d3bb3 Fix User Documentation link in index.dox (#4410)
Updated the link to the User Documentation by removing the trailing slash, which was pointing to an incorrectly rendered page
2026-07-08 13:48:01 +03:00
Mykhailo ShevchukandClaude Opus 4.8 71280128a3 Apply clang-format to recently merged NFC/RPC/CCID files [ci skip]
Run `fbt format` over the tree; normalizes whitespace, comment
spacing and argument wrapping in the bambu parser, Network/GPS RPC
services and ccid_test USB layer. No functional change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 00:05:57 +03:00
MX 473476300a bump apps tag 2026-07-07 23:35:53 +03:00
fd2ec5878d [SubGHz] Fix crash when exiting CLI subghz chat with external CC1101 (#1036)
* [SubGHz] Fix crash when exiting CLI `subghz chat` with external CC1101

Pressing Ctrl+C to exit `subghz chat` while using an external CC1101
crashed the Flipper (null-pointer dereference + reboot).

The exit path called subghz_devices_deinit() before stopping the chat
worker. deinit unloads the external CC1101 radio-device plugin, freeing
its SubGhzDevice struct and interconnect vtable; the TxRx worker thread
sleeps/ends that device from its own thread on shutdown, so joining it
after deinit dereferenced freed plugin memory. The internal CC1101 is a
static device that deinit never frees, which is why the crash was
external-only.

Stop and free the worker before subghz_devices_deinit(), matching the
teardown order already used by subghz_cli_command_rx. Also add the
missing deinit + radio power-off to the "tx not allowed" early return so
a subsequent subghz command's subghz_devices_init() does not
furi_check-fail on an already-valid registry.

Fixes #829

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* changelog: SubGHz crash exiting CLI chat with external CC1101 (PR #1036, Fixes #829)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 23:09:05 +03:00
e4e8a8ef2b [NFC] Trim MIFARE Plus generator & admin-key mapping (in-place cleanup) (#1035)
* [NFC] Data-drive MIFARE Plus manual-add generators

Replace the 18 near-identical MIFARE Plus generator thunks and their
handler-table entries with a data-driven mf_plus_generator_configs[]
table (uid_len / type / size / ATS per variant), dispatched via a small
range predicate. The Ultralight/NTAG and Classic types keep their
bespoke handlers, so the handler table now stops before the MF Plus
range.

Also drop mf_plus_ats_hist_ev, which was byte-identical to
mf_plus_ats_hist_s (EV1/EV2 are identified by GetVersion, not the ATS).

Pure refactor: the Add-Manually menu entries, the generated cards and the
public SDK API are all unchanged. Trims firmware flash by removing the
per-variant functions and the duplicate blob; a _Static_assert pins the
config table to the enum range so a future type can't desync it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus: single source for admin-key addresses

Fold the three copies of the admin-key address<->type mapping
(0x9000 CardMaster, 0x9001 CardConfig, 0x9003 L3Switch, 0x9004
SL1CardAuth) into one mf_plus_admin_key_addresses[] table with a forward
helper (mf_plus_get_admin_key_address, replaces the poller's local id
array) and a reverse helper (mf_plus_admin_key_type_from_address,
replaces two identical switch statements in the listener's key-resolve
and write-store paths).

Behavior-preserving: both listener default fall-throughs are kept (resolve
returns false; store falls through to data/config block handling), the
poller authenticates the same addresses, and a _Static_assert pins the
table to MfPlusAdminKeyNum. Internal helpers only; no SDK API change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] Changelog: MIFARE Plus generator/admin-map cleanup

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:08:54 +03:00
45c3762c06 [NFC]: Added support for Mifare Plus in SL3 to NFC (#1032)
* [NFC] Add MIFARE Plus SL3 crypto primitives (mf_plus_crypto)

Foundation for native MIFARE Plus SL3 support: AES-128 ECB/CBC (firmware mbedtls), AES-CMAC + the MFP 8-byte truncation, AN10922 session-key derivation, MAC assembly, and the data-encryption IV builders.

IV layout follows Proxmark3 mfp_data_crypt(), verified against source: single low counter byte; read R_lo@[0/4/8]+TI, write W_lo@[7/11/15]+TI. The write-direction IV is not yet hardware-validated -- flagged in-header for confirmation before the write/emulation paths (PR6/7).

No callers yet; poller/listener land in later PRs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [NFC] Add RFC 4493 self-test for MIFARE Plus SL3 crypto

Known-answer vectors for mf_plus_crypto: the four RFC 4493 AES-CMAC cases (empty / single / partial-final / multi-block) plus the cmac8 odd-index truncation, and a regression test locking the PM3-matched data-IV byte layout (read R_lo@[0/4/8]+TI, write W_lo@[7/11/15]+TI, low-byte-only).

Added to the nfc unit-test suite; the four mf_plus_crypto_* symbols are exported via the test-only API table (unit_test_api_table, api 0.0) so the test plugin resolves them at runtime. Builds clean (firmware + test_nfc plugin); on-device run via the unit_tests CLI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [NFC] Extend MfPlusData with SL3 block/key model + versioned dump

PR2 of native MIFARE Plus SL3. MfPlusData now holds SL3 content: data blocks + a read bitmap, per-sector AES KeyA/KeyB (+ found masks), admin keys (CMK/CCK/L3-switch/SL1-auth), config blocks (0xB0xx), and the originality signature. Keys are stored separately from block data (SL3 keys are in the 0x40xx keyspace, not data memory, unlike Classic's sector-trailer layout).

Adds a versioned .nfc save/load mirroring mf_classic (dense 'hex or ??' per-block lines, a 'Data format version' key, backward-compatible with legacy metadata-only dumps). is_equal now diffs blocks+keys+config+signature so emulation writeback to .shd can be detected.

Review hardening: mf_plus_str_to_bytes parses into a local and commits on success (out untouched on failure), guards a truncated-line OOB read, and logs corrupt hex vs a legitimate '??'; load refuses an unknown newer data-format version; alloc zeroes via reset; compile-time asserts lock the mask-fits-domain invariants.

Builds clean (fbt firmware_all). Gated: code/silent-failure/type-design/comment review + simplify.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [NFC] Add MIFARE Plus SL3 read poller (auth + read + signature)

PR3 of native MIFARE Plus SL3. Extends the previously detection-only mf_plus poller with the SL3 secure-messaging read flow, composing over the existing iso14443_4a poller.

Primitives (ported byte-faithfully from the flipperzero-mfp-reader fork onto the PR1 crypto): AuthFirst (0x70/0x72) with the rrot(RndA)/rol(RndB) token + session-key derivation, encrypted+MAC'd block read (0x31) with the pre/post-increment r_ctr counter model, and the plaintext originality signature (Read_Sig 0x3C).

State machine (mirroring mf_classic's mode/event API): after detection, RequestMode -> ReadSignature -> per-sector RequestKey -> AuthSector -> ReadSectorBlocks -> ReadSuccess, gated on SL3 + read mode. The app supplies candidate keys via RequestKey and tracks progress via DataUpdate; RequestMode defaults to Info so the existing detect/info scan is unchanged. The app-side dictionary driver lands in PR4.

Also lands the PR2-deferred accessor family + per-sector geometry and routes the serializer through the atomic setters.

Review hardening: a genuine wrong key (Auth) advances to the next candidate while a comms fault fails loudly instead of silently dropping a key; a response-MAC mismatch aborts the sector (counter desync) rather than cascading; the SL3 path is logged throughout.

Builds clean (fbt firmware_all). Gated: code + silent-failure review + simplify. On-device read of a real SL3 card is manual QA.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [NFC] Add MIFARE Plus SL3 dictionary attack

Recover SL3 sector AES keys by dictionary and read all sectors, exposed as
"Unlock with Dictionary" on the read and saved menus (gated on SL3; SL0/1/2
never reach this path).

- New scene nfc_scene_mf_plus_dict_attack: the SL3 poller has no
  resume-from-prior-data path, so the user and built-in system dictionaries are
  consumed in a SINGLE poller pass (user keys first) rather than as separate
  runs, which would discard already-recovered keys. Restarts the combined key
  stream per (sector, key type) since the poller has no NextSector event.
- System dictionary asset (mf_plus_dict.nfc): union of PM3
  g_mifare_plus_default_keys and client/dictionaries/mfp_default_keys.dic,
  deduplicated and upper-cased (33 AES-128 keys).
- Reuses the shared dict_attack view in its sector-oriented layout; sets the
  type explicitly on entry (the shared view keeps whatever type the last scene
  left it in).
- mf_plus_is_card_read(): a block-completeness check used to grade the outcome
  full vs semi success, instead of the poller's sector counter (which also
  counts sectors that aborted mid-read). A terminal ReadFailed (lost card /
  comms fault) is logged and always graded semi-success, never a clean read.
- Expose mf_plus_get_sector_count and mf_plus_is_card_read in the public SDK
  API (the NFC app is an external FAP and calls them), mirroring the mf_classic
  geometry/status helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] Add MIFARE Plus user key management scenes

Add "MIFARE Plus Keys" to Extra Actions, mirroring the MIFARE Classic and
Ultralight C key managers, so users can grow the SL3 dictionary the "Unlock
with Dictionary" attack consults.

- Scenes: keys (Add/List menu with system+user key counts), keys_add (byte
  input, rejects duplicates), keys_list (pick a user key), keys_delete (confirm
  + delete), keys_warn_duplicate. Keys are 16-byte AES-128, stored in the
  lazily-created user dictionary (NFC_APP_MF_PLUS_DICT_USER_PATH).
- keys_delete hoists the load-key-at-index walk into a helper shared by
  on_enter and on_event and tolerates a stale index (falls back to the list)
  instead of asserting; keys_list uses a distinct inner loop index rather than
  shadowing the outer one.
- Route save/delete success back to the MIFARE Plus keys menu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] Recover MIFARE Plus SL3 admin keys and config blocks

Extend the SL3 read scan with an admin-key phase after the sectors: recover the
0x90xx admin keys by dictionary and, once the Card Master or Config key is
found, read the 0xB0xx configuration blocks with it.

- Generalize the auth primitive to a raw 16-bit key address
  (mf_plus_poller_authenticate_key_id); the sector variant now delegates to it.
  Admin key addresses: CMK 0x9000, CCK 0x9001, L3 switch 0x9003, SL1 auth 0x9004
  (the enum skips 0x9002, so a lookup table maps them). Auth is the same
  0x70/0x72 flow as sector keys, little-endian address.
- Generalize the encrypted read with a block-address high byte: data blocks
  pass 0x00, config blocks pass 0xB0 (both in the frame and the command MAC).
- New states RequestAdminKey -> AuthAdminKey -> ReadConfig. Config is read
  interleaved while the CMK/CCK session is fresh, and tracked by the per-block
  config mask so a CCK pass fills blocks a CMK pass could not read.
- The admin/config phase is best-effort (the sectors are already captured before
  it runs): a wrong candidate asks for the next, an absent admin slot (AUTH_FIRST
  rejection) is skipped, and a lost card finishes with the complete data instead
  of failing an otherwise-complete read.
- The dictionary-attack scene answers admin-key requests from the same
  dictionaries and restarts the key stream when the request target changes
  (sector key <-> admin key, or which admin key).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] Render recovered MIFARE Plus SL3 content

The "More info" view showed only GetVersion data; now it also dumps the SL3
content recovered by the dictionary attack: originality signature, sector keys
A/B, admin keys (0x90xx), config blocks (0xB0xx) and the data blocks, each shown
as hex or "??" when not recovered. Section is gated on SL3 (the poller populates
this content only for SL3 read mode).

- Expose the read-only accessors (mf_plus_get_block_count, mf_plus_is_block_read,
  mf_plus_is_key_found, mf_plus_is_admin_key_found, mf_plus_is_config_block_read)
  and a mf_plus_get_admin_key_name getter in the public SDK API so the NFC app
  (an external FAP) can render them; the matching set_* accessors stay internal.
  Mirrors the mf_classic public accessor surface. Folded into the existing 87.12
  API bump for this feature (no further version bump).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] Add MIFARE Plus SL3 native listener (auth + read emulation)

Emulate a recovered SL3 card so a reader can authenticate and read it. The
listener is the card-side mirror of the read poller, reusing the same
mf_plus_crypto primitives with reader/card roles reversed.

- mf_plus_listener composes over the ISO14443-4A listener (registered in
  nfc_listener_defs.c). run() dispatches received APDUs on the command byte.
- AUTH_FIRST (0x70): resolve the sector/admin key from the recovered MfPlusData,
  generate RndB, reply E(key, RndB). AUTH_CONTINUE (0x72): verify the RndB echo,
  derive the session keys from rrot(RndA)+RndB (same inputs as the poller), and
  echo rol(RndA) so both this port's poller and a PM3-style reader accept it.
- READ (0x31): verify the command MAC (pre-increment r_ctr), then reply with the
  encrypted block + response MAC using the post-increment counter/IV, byte-for-
  byte matching the poller's read. Serves data blocks and 0xB0xx config blocks.
- READ_SIG (0x3C): returns the stored originality signature. Unknown commands
  (GetVersion, WritePerso probe) are left unanswered so the reader falls back to
  ATS identification.
- Runtime get_features gate: SL3 -> EmulateFull, SL0/1/2 -> EmulateUid only, so
  non-SL3 cards are not offered full emulation. Emulate scene allocates the
  native MfPlus listener for SL3 (required for a future .shd writeback) and
  falls back to a UID-only ISO14443-4A listener otherwise.

Reader-write handling and .shd writeback come next; the write path is not yet
included here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MFP: sector read-progress helper + drop the empty signature dump line

- mf_plus_get_read_sectors_and_keys(): counts fully-read sectors (all blocks
  captured) and recovered sector keys, for the read-progress screens (mirrors
  mf_classic_get_read_sectors_and_keys). Public so the app FAP can use it.
- The originality signature is now written to the .nfc last and only when the
  card actually has one; a card with no signature (MFP X/EV0) no longer gets a
  misleading "Signature: ??" line, and its absence loads as "no signature".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MFP: align read-success and info screens with MIFARE Classic

The read-success and info screens now show name / UID / Keys Found / Sectors
Read (and Tech / ATQA / SAK on the full info), matching the MIFARE Classic
layout via nfc_render_iso14443_3a_info + the new sectors/keys helper. The
ISO14443-4 protocol detail (ATS, bit rates) moved from the info screen into
"More info", where the full SL3 dump lives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MFP: auto-run the dictionary attack after reading an SL3 card

The read poller callback now reports the SL3 identity scan as "incomplete" and
the read scene auto-continues into the dictionary attack, so recovering keys no
longer requires the manual "Unlock with Dictionary" tap -- mirroring the MIFARE
Classic read flow. SL0/SL1/SL2 finish at the read-success screen as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MFP: group Keys under MIFARE Classic; shorten the save-name prefix

- Move "MIFARE Plus Keys" directly under "MIFARE Classic Keys" in Extra Actions.
- Abbreviate " Plus" -> "P" in the auto-generated save name, so MIFARE Plus
  dumps prefill as "MFP..." (and NTAG I2C Plus as "NTAG I2CP...") instead of the
  "MF+" the generic " Plus" -> "+" rule produced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: support plaintext (0x33) block reads

Some SL3 cards require plaintext block reads (command 0x33) instead of the
encrypted default (0x31), governed by the per-block access conditions. A card
like the MIFARE Plus S rejects 0x31 with status 0x0B ("command not available
at current card state") on every data block, so it authenticates all keys yet
reads zero sectors.

Poller: mf_plus_poller_read_block() gains a `plain` flag selecting the opcode;
in plain mode the data travels in the clear (no CBC-decrypt) and the response
MAC is verified over the transmitted plaintext. A new MfPlusError value,
Rejected, distinguishes a card status-byte refusal (a bare status frame that
leaves r_ctr in sync) from a comms/RF fault (Protocol).

The sector-read handler probes the mode in-line: on a Rejected while the mode
is still unlocked, it re-authenticates the sector (resetting the counter) and
retries the SAME block in the other mode, adopting that mode card-wide only on
a successful read. A transient comms fault never flips the mode, and a block
refused in both modes is skipped while the next block re-probes, so neither an
encrypted nor a plain-only card can be mislocked by an unreadable first block.

Listener: mf_plus_listener_read_handler() answers both 0x31 and 0x33 (plaintext
+ response MAC), so an emulated S-style card round-trips.

SL0/SL1/SL2 are unaffected; config (0xB0xx) reads stay encrypted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: answer WritePerso probe in the listener

A reader's info scan (PM3 `hf mfp info`) identifies a MIFARE Plus card by
sending the SL0-only WritePerso command (0xA8) to the invalid block 0x9090 and
reading the rejection status byte: a card in SL3 refuses it with 0x0B ("command
not available at the current card state"), which the reader reads as Plus + SL3.

Our SL3 listener left 0xA8 unanswered, so an emulated card returned "No card
response" to that probe and the reader printed "Identification failed" even
though UID/ATQA/SAK/ATS were all emulated correctly. Add a stateless handler
that answers WritePerso with 0x0B.

WritePerso is rejected by a real card regardless of block address or
authentication state, so the handler ignores the frame and needs no session; it
touches neither the listener state nor the crypto counters. GetVersion (0x60)
and Read_Sig (0x3C) are intentionally still left unanswered for an EV0/X/SE
card, which genuinely has neither -- matching real-card behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: NAK unsupported commands in the listener

A reader's info scan (PM3 `hf mfp info` / `hf 14a info`) sends GetVersion (0x60)
and Read_Sig (0x3C) while identifying a card. Our SL3 listener left those (and
any other unimplemented command) unanswered, so an emulated card returned an
empty frame and the reader printed "No card response" for each -- an EV0/S/X
part has neither command, but a real one still answers rather than going mute.

A real MIFARE Plus S 2K (EV0) confirms this: to GetVersion it replies with a
bare 0x0B status ("command not available at the current card state"):

    Rdr | 02 90 60 00 00 00 ..  | GET VERSION
    Tag | 02 0B ..              | INF = 0x0B

Mirror that: unimplemented commands, and Read_Sig on a card with no signature,
now NAK with 0x0B via a small mf_plus_listener_send_status() helper (shared with
the WritePerso handler). The reader's scan completes instead of timing out, and
reading the emulation back still falls through to ATS-based typing (GetVersion
0x0B -> version parse fails -> ATS; Read_Sig 0x0B -> signature absent).

GetVersion is intentionally not answered with a fabricated version: 0x0B is what
a real EV0 returns. Replaying a stored version for EV1/EV2 is a later item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus: drop the manual "Unlock with Dictionary" menu item

The SL3 dictionary attack now runs automatically on read (the read scene enters
the dict-attack scene on PollerIncomplete), so the manual "Unlock with
Dictionary" entry on the read and saved menus was redundant. Remove it: delete
the submenu item, its enum, and its event handler, and point both menus at the
common empty scene handlers. The standard Save/Emulate/Info/etc. items are added
by the framework and are unaffected.

The dict-attack scene stays reachable through the automatic read flow; only the
manual entry point is gone. mf_plus_is_sl3 is kept (the runtime feature gate
still uses it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: NAK failed authentication in the listener

When a reader (or key check like PM3 `hf mfp chk`) authenticates with a wrong
key, our SL3 listener answered AUTH_FIRST but then went silent on the
AUTH_CONTINUE whose RndB echo didn't match. The reader timed out and desynced
its whole scan -- a sector with a custom key poisoned the checks of later
factory-key sectors.

A real MIFARE Plus S 2K (EV0) confirms the correct behaviour: to a wrong-key
AUTH_CONTINUE it replies with a bare 0x06 status (authentication error):

    Rdr | 0B 00 72 D0 78 ..  | SECOND AUTH STEP (wrong key)
    Tag | 0B 00 06 84 E9     | INF = 0x06

Mirror that. Every auth failure -- malformed AUTH_FIRST/AUTH_CONTINUE, a key
the emulated (partial) dump never recovered, AUTH_CONTINUE without a prior
AUTH_FIRST, and the wrong-key RndB-echo mismatch -- now NAKs with 0x06 via the
shared status helper instead of staying silent. The reader records a miss and
advances to the next key. Our own poller already reads a non-0x90 AUTH_CONTINUE
status as MfPlusErrorAuth ("wrong key, next candidate"), so reading the
emulation back is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: add a "Show Keys" screen

Mirror the MIFARE Classic "Show Keys" viewer for MIFARE Plus SL3. A new scene
lists the recovered per-sector AES keys (A/B) and the four admin keys (Card
Master, Card Config, L3 Switch, SL1 Card Auth) in a scrollable widget, offered
via a "Show Keys" item on both the read and saved menus for SL3 cards.

Like the MFC viewer it lists only sectors with a recovered key and omits the
unknown ones to stay compact on a partially-read card; keys print as compact
uppercase hex (16-byte AES keys are too long for per-byte spacing). It adds an
explicit "No keys recovered yet." line for the empty case and a footer with the
A / B / admin found counts. The item is always present for SL3 so the view is
reachable even before any key is recovered.

All accessors and key fields are already public, so no API change is needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: switch data IV to the fork's validated two-counter layout

The data-encryption IV builders followed PM3's single-low-byte layout, which is
only PM3-source-derived and never hardware-validated for the write direction.
Switch both to the reference fork's layout instead: a little-endian
{R_ctr, W_ctr} word {R_lo, R_hi, W_lo, W_hi} repeated across the IV, with TI at
IV[12..15] for reads and IV[0..3] for writes. This is the layout validated
end-to-end by a U-Prox encoder writing keys to the fork's emulation, so it is
the trustworthy basis for the encrypted write path (0xA1) coming next.

For a read-only session (W_ctr == 0, R_ctr < 256) the word collapses to
{R_lo, 0, 0, 0} and the read IV is byte-for-byte identical to the old PM3
layout, so the hardware-validated read path is unaffected -- both read-IV
callers (poller, listener) simply pass their session's W_ctr (0 until a write).

Unit-test IV vectors updated to the new layout, including two full-counter
cases that pin every R/W byte position; the test-only API-table signatures
follow the new 4-arg form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: emulate writes (0xA1/0xA3) for .shd writeback

Add the card-side write handler to the SL3 listener so a reader writing to the
emulated card mutates MfPlusData, which the generic emulate-exit diff then
auto-saves as a .shd. This completes the emulation-with-change path (e.g. a
U-Prox encoder re-keying the emulated card).

The handler is a native port of the reference fork's validated handle_write:
- Frame [cmd, BNr_lo, BNr_hi, data(16), cmd_mac(8)]. Command MAC over
  {BNr_lo, BNr_hi, data-as-transmitted} with the pre-increment W_ctr; response
  is 0x90 + MAC over the post-increment W_ctr (mirrors the read counter model).
- 0xA1 (keys) CBC-decrypts the block with the write IV; 0xA3 (data) carries
  plaintext. W_ctr advances only on an accepted write, so a NAK (bad MAC, short
  frame, unknown address, not authenticated) never desyncs the reader.
- store_write resolves the 2-byte address to a sector key (0x40xx), admin key
  (0x90xx), data block (high 0x00) or config block (0xB0xx) and calls the
  matching mf_plus_set_* mutator (field + found/read mask), so is_equal detects
  the change and a key write rotates the stored key for the re-auth round-trip.

Not yet hardware-validated: the encrypted-write IV rests on the fork's
U-Prox-validated layout (see the crypto commit), to be confirmed on-device.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: poller write path (write-to-card)

Add the reader-side write to the SL3 poller, the card-side counterpart of the
already-validated listener write handler. This is the protocol core of "Write
to Initial Card"; the UI wiring follows in a separate commit.

- mf_plus_poller_write_block(): 0xA1 encrypted (CBC-encrypt the block with the
  write IV) / 0xA3 plain, mirroring mf_plus_poller_read_block. Command MAC over
  {block_low, block_high, data-as-transmitted} with the pre-increment W_ctr; a
  status rejection (0x0B) returns MfPlusErrorRejected without advancing W_ctr,
  an accepted write advances it once and verifies the response MAC over the
  post-increment value.
- Write state machine: RequestWriteSector (ask the app for the sector's
  recovered key) -> AuthWriteSector -> RequestWriteBlock (ask for the block
  data) -> WriteBlock. Writes only the sectors/blocks present in the dump; a
  sector whose key isn't in the dump is skipped.
- Write-mode probe: like the read probe, the first block is tried encrypted and,
  on a status rejection, the sector is re-authenticated and the block retried
  plain, adopting whichever mode succeeds card-wide. The user's note that write,
  like read, has both plain and encrypted forms is handled here.
- New MfPlusPollerModeWrite + RequestWriteSector/RequestWriteBlock/WriteSuccess/
  WriteFailed events; the ISO14443-4a fault path reports WriteFailed in write
  mode.

Not yet hardware-validated on the poller side (the write crypto itself is
HW-proven via the listener + U-Prox); confirm with a real-card write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: "Write to Initial Card" UI

Wire the app UI for writing a saved SL3 dump back to its source card, using the
generic write-scene machinery (the same approach as MIFARE Classic).

- Advertise NfcProtocolFeatureWrite for SL3 only (runtime get_features gate), so
  SL0/1/2 -- which have no recovered blocks -- never offer it.
- The generic "Write" item is relabelled "Write to Initial Card" on the saved
  menu and removed from the read menu (writing right after a read makes no
  sense), mirroring MIFARE Classic. Both menus keep "Show Keys".
- The write scene starts the poller in write mode; its callback gates on a UID
  match (write only the card the dump came from), then feeds the recovered
  sector keys (preferring key A) and data blocks from the loaded dump. A
  sector/block the dump lacks is skipped by leaving key/block_provided false.

Completes the "Write to Initial Card" feature (poller path landed in the
previous commit). Not yet hardware-validated end-to-end; confirm on a real card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: "Update from Initial Card"

Add the re-read-and-refresh flow (card -> saved dump + shadow), mirroring
MIFARE Classic. Offered on the saved menu for SL3; runs no dictionary attack --
it re-reads the source card using the dump's already-recovered keys.

- Three scenes (update_initial + success + wrong_card) mirroring the MIFARE
  Classic ones. The worker runs the read poller in read mode: UID-gate on
  RequestMode, then feed the recovered sector/admin keys from the dump.
- Loop guard: the read poller re-requests a (sector, key) after a failed auth to
  get the next dictionary candidate, but here there is only the one recovered
  key -- so a re-keyed card would loop. feed_once() feeds each key exactly once
  (scene state = last-fed-id + 1) and answers "no key" on an immediate repeat.
- Merge, don't replace: the poller reports ReadSuccess even if its best-effort
  admin/config/signature phase is cut short (early card lift), so a wholesale
  set_data could silently drop keys/config the dump already had. New public
  helper mf_plus_merge_update() overlays every field the re-read recovered onto
  a clone of the old dump without clearing anything, so the refresh only adds/
  updates. Exposed in api_symbols.csv (still 87.12) for the FAP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus: detect S/X/SE by ATS nibbles, not full match

A real MIFARE Plus SE presents ATS historical bytes C1 05 21 30 00 77 C1 --
the trailing bytes (caps + CRC16) differ from the AN10833 values (...F6 D1),
so the exact 7-byte match failed and the SE was mis-detected as a generic Plus.

Match the discriminating nibbles instead, the way PM3 does. The block is
C1 05 <type/size> <gen> <caps> <crc16>; only the type/size nibbles are stable:
- S / X mask their size (byte2/3 = 2F 2F); the caps byte's SVC bit separates
  them (set = X, clear = S).
- SE reveals its size (byte2 = 0x21 = 1K, byte3 = 0x30).
The C1 05 + high-nibble-2 gate keeps a DESFire (also SAK 0x20) from being
mis-claimed. The SAK 0x08/0x18 SL1 cases now use the same matcher, so SE 1K SL1
is recognized too while S/X classify exactly as before.

Removes the now-unused mf_plus_ats_t1_tk_values table (its SE entries held the
unreliable AN10833 tail); the mf_classic SL1 probe keeps its own local S/X pair.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [gui] elements_text_box: word-wrap instead of breaking mid-word

The multiline text box wrapped character by character, so a long line broke in
the middle of a word (e.g. an emulated "MIFARE Plus S 2K SL3" name split as
"...2K S" / "L3"). Track the last space on the current line and, on overflow,
break there so the whole trailing word moves to the next line; fall back to the
original character wrap when a single word is itself wider than the box.

A word containing an inline font marker (\e#, \e*, \e!) still character-wraps:
rewinding across an already-applied marker would re-toggle the emphasis and
corrupt the width measurement, so `marker_since_space` blocks the word-wrap
branch for those words.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: MFC-style block dump + ISO14443-4 "More" page

Rework the SL3 "More info" so the block layout reads like MIFARE Classic and the
protocol/hardware detail moves to a second page.

- The block dump is now per-sector: each block as hex, and the sector trailer
  shows the recovered Key A + the trailer block's access bits (its bytes 6-9) +
  Key B. SL3 keeps the AES keys in a separate keyspace, so the raw trailer's key
  bytes are meaningless -- substituting the recovered keys is what makes it read
  like a Classic dump. The separate "Sector Keys" section is dropped (its keys
  now live in the trailers). Hex is emitted in space-separated 2-byte groups so
  long lines wrap between groups.
- nfc_render_mf_plus_data is split into nfc_render_mf_plus_dump (blocks + admin
  keys + config + signature) and nfc_render_mf_plus_iso4 (GetVersion +
  ISO14443-4). The block-dump page gains a "More" button to a new
  MfPlusIso4Info scene holding the ISO14443-4 details that used to be inline.
- mf_plus_sector_get_first_block / _block_count are promoted to the public
  header (+ api_symbols.csv, still 87.12) so the app-side render can walk the
  per-sector geometry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: answer AUTH_FIRST for unrecovered sectors

When emulating a partially-recovered dump, a reader authenticating a sector
whose key we never recovered was NAK'd at AUTH_FIRST (phase 1) with 0x06. A real
card always answers AUTH_FIRST -- it holds every key and only rejects a wrong
key at AUTH_CONTINUE -- so a phase-1 NAK is unlike any real card, and some
readers (a U-Prox encoder) abort the whole exchange when they see it, leaving
the auth "unfinished".

Verified on hardware: to AUTH_FIRST the real card answers 90 02 BE AA.. (0x90 +
E(RndB)); our emulation answered 06 58 B3.

Now an unrecovered key runs the handshake with a random placeholder: phase 1
completes normally, and since the placeholder can't match the reader's key, the
reader's phase-2 token fails to echo our RndB and we NAK 0x06 at AUTH_CONTINUE
-- exactly as a real card rejects a wrong key. No false-successful auth (the
reader would have to guess our per-session random 16 bytes, and even then the
sector's blocks were never recovered so nothing is exposed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus: restructure More-info into a hub + fix dump OOM

The Info screen's "More" button now opens a small hub (View Dump /
ISO14443-4 Data), DESFire-style, instead of the block dump directly:

- View Dump (SL3 only): the Classic-style block dump, now rendered in a
  memory-light text_box (stores the pointer, lays out visible lines
  lazily) rather than a text-scroll widget. The widget deep-copies the
  whole string AND allocates a FuriString per wrapped line, which for a
  multi-KB 2K/4K dump exploded into hundreds of allocations and OOM'd on
  the ~30 KB-free NFC thread. Also furi_string_reserve the dump up front
  so the many cat calls don't grow by doubling (that transient ~2x
  realloc was the intermittent "opened on the 3rd try" OOM).
- ISO14443-4 Data: the ISO14443-4 protocol details as a widget with a
  "More" button to a new GetVersion page.

Dump layout: block number on its own line with hex below, so a 16-byte
block wraps into two even 2-byte-group lines instead of colliding with
the "N:" prefix; dropped the leading blank line.

Splits nfc_render_mf_plus_iso4 into nfc_render_mf_plus_iso14443_4 +
nfc_render_mf_plus_version_info for the two new pages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus: add 18 "Add Manually" variants

Add SE / S 2K / S 4K / X 2K / X 4K / EV1 2K+4K / EV2 2K+4K generators,
each in a 4-byte and 7-byte UID flavour, to the core nfc_data_generator
(they auto-appear in the app's "Add Manually" list, which iterates the
generator enum). All are built at SL3 -- the native ISO14443-4
presentation this app fully supports -- with a random NXP UID,
product-correct ATQA/SAK, and a well-formed ATS.

Defaults follow real/known values where available and sensible
placeholders otherwise:
- ATS historical bytes: SE uses a real capture (C1 05 21 30 00 77 C1);
  S/X use AN10833 (2F 2F, SVC bit distinguishes them) -- validated
  byte-identical against real S/X cards' ATS. EV1/EV2 identify via
  GetVersion so they reuse the generic Plus block. S/X share their ATS
  across 2K/4K (the size is masked there; ATQA carries it).
- EV1/EV2 get a plausible GetVersion response (hw_major 0x11/0x22,
  hw_storage 0x16/0x18) and a random 56-byte originality signature; the
  EV0 products (SE/S/X) have neither, matching real silicon.
- SL3 sector/admin keys and blocks are left empty -- a synthetic card
  has no recovered keys.

Generated cards round-trip through save/load and re-detect as the
intended product (type/size/SL are stored explicitly).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] Add Manually: keep the list cursor on the picked type

Opening a card type from "Add Manually" and pressing Back re-entered the
type list at the top instead of on the row just opened. The set_type
scene rebuilds its submenu on every entry (including on Back) but never
restored the selection. Save the picked row in the scene state (stored
+1 so the default 0 means "nothing picked yet" -> top) and restore it in
on_enter. Applies to every protocol's Add Manually list, not just
MIFARE Plus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] iso14443-4: don't crash on an I-block with no INF field

Decoding a received I-block copied its INF field with
bit_buffer_copy_right(input, prologue_len), which furi_check-crashes
(bit_buffer.c: size must be > start_index) when the I-block carries no
INF at all -- total size <= its prologue length. A bare PCB-only
I-block, or a CID-flagged empty I-block on a CID-supporting card, trips
it. Every other malformed case in this decoder already returns Skip;
this one asserted and took the whole device down.

Guard it the same way: skip an I-block whose size is <= its prologue
length instead of asserting. Also catches a truncated frame whose PCB
claims a CID/NAD byte it is too short to contain.

Found on hardware emulating a MIFARE Plus SL3 card to an NXP TagInfo
reader: TagInfo desyncs into an R(NAK) retransmission storm and emits
empty I-blocks, crashing within seconds. Fix is in the shared 14443-4
transport, so it hardens all 14443-4 emulation (DESFire, Type-4, ...),
not just MIFARE Plus. HW-confirmed: no more crash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus: recognize the second SE ATS fingerprint

NXP TagInfo (v6.2.0, reverse-engineered) matches TWO Plus SE ATS
historical-byte fingerprints -- C1 05 21 30 00 77 C1 and
C1 05 20 30 00 AB 9B -- differing only in the size/gen nibble (byte2
0x21 vs 0x20). We only matched 0x21, so a real SE carrying the 0x20
fingerprint fell through to Unknown. Match SE on byte3 == 0x30 with
byte2 in {0x20, 0x21}; S/X keep byte3 == 0x2F, so no overlap and no
regression. Real S/X ATS are already byte-identical to TagInfo's table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus: emulate GetVersion for EV1/EV2

An emulated EV1/EV2 now answers GetVersion so a reader (NXP TagInfo,
PM3) classifies it correctly instead of falling back to a generic Plus.
GetVersion is an application-level command (no transport support), so
the listener crafts the chained response itself.

It replays the stored 28-byte MfPlusVersion as three frames, matching a
real EV1 capture byte-for-byte:
  0x60 -> 0xAF + 7 HW bytes
  0xAF -> 0xAF + 7 SW bytes
  0xAF -> 0x90 + 14 UID/batch/date bytes   (final status 0x90, NOT
                                            DESFire's 0x00)
Both the native form and the ISO7816-wrapped 0x90-CLA form (data first,
0x91 <status> trailer) are handled, with a small get_version_stage
chain-state on the listener (reset on field-off/halt and when any other
command interrupts the chain).

Gated on a real version being present (EV1/EV2: hw_vendor 0x04, family
nibble 0x02); EV0 parts (S/X/SE) have none and keep NAKing 0x0B, which
is faithful and is TagInfo's EV gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus Add Manually: blank-format memory + real EV1 version

Manually-added MIFARE Plus cards were identity-only: every block and key
read as "??", which is useless to emulate or inspect. Fill a
blank-formatted factory state, the MIFARE-Plus analogue of the Classic
generator's 0x00 blocks + default keys:
- data blocks zeroed and marked read; block 0 rebuilt as the manufacturer
  block (UID + BCC on 4-byte UIDs + SAK + ATQA + 0xFF), since
  mf_plus_set_uid doesn't populate block 0
- every sector and admin AES key set to the all-FF default and marked
  found; config blocks zeroed

Also align the EV1/EV2 generator version bytes to a real EV1 GetVersion
capture (hw_proto 0x04; the software version reports its own major/minor
0x01/0x01, independent of the hardware major), so an emulated Add-Manually
EV card's GetVersion matches real silicon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] docs: MIFARE Plus detection reference (NXP TagInfo RE)

Static-analysis notes on how NXP TagInfo classifies a MIFARE Plus card
(variant / security level / size) via SAK routing, ATS fingerprinting,
GetVersion, Read-Signature and the VCA applet -- the reference our
detection and emulation are aligned to.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: changelog entry + API 87.12

Add the "Native MIFARE Plus support in SL3" changelog line and bump the
listed Current API from 87.11 to 87.12 (the single API bump this feature
introduces).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] Show full raw ATS on the ISO14443-4 info screen

Add the complete ATS frame (TL T0 [TA1] [TB1] [TC1] + historical bytes) as a
raw line at the top of the shared ISO14443-4a extra renderer, before the
decoded protocol info, so every 14443-4 protocol (MIFARE Plus, DESFire,
NTAG4xx, Type-4, plain 14443-4a) exposes the exact ATS. The dedicated
[Historical bytes] section is kept, mirroring PM3's separate ATS and
Historical bytes blocks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] Cache recovered MIFARE Plus SL3 keys per UID

Mirror the MIFARE Classic key cache for SL3: on save, persist the recovered
sector AES keys (A/B) and 0x90xx admin keys to /ext/nfc/.cache/{UID}.mfpkeys.
On a later read, feed the cached key for each (sector, key type) / admin
target before the dictionaries, so a known card authenticates on the first
try instead of re-running the whole dictionary attack. A wrong cached key
(re-keyed card) falls through to the dictionaries, and a cache miss runs the
attack exactly as before.

A distinct .mfpkeys extension and file header keep it from colliding with the
MIFARE Classic {UID}.keys cache (a Plus card read in SL1 is MIFARE Classic).
The cache helper is bundled into the nfc_mf_plus plugin sources (its own link
unit), mirroring nfc_mf_classic. SL0/SL1/SL2 are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [NFC] MIFARE Plus SL3: note per-UID key cache in changelog

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [GUI] Fix elements_text_box line[] overflow on 8+ line boxes

elements_text_box bounded line_num only by the height break, which on a
near-full-height box (>=63px tall with 9px leading) commits a 7th line and then
writes line[ELEMENTS_MAX_LINES_NUM] -- index 7 of a 7-element array -- a stack
out-of-bounds write. The word-wrap change (less dense than char wrap) lets text
that previously fit in <=7 lines reach 8, exposing the latent gap. Add the
missing line_num >= ELEMENTS_MAX_LINES_NUM bound so line[] is never indexed past
its last slot. Hardens the shared primitive for every firmware text box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 21:01:10 +03:00
MX 68659ee8cd OFW CCID: move USB layer from firmware HAL into ccid_test app [ci skip]
https://github.com/flipperdevices/flipperzero-firmware/pull/4407/changes
2026-07-07 20:49:34 +03:00
b1b1f94c42 [NFC] Fix memory leaks & double-frees in supported-card parsers, app lifecycle & DESFire (#1030)
* nfc: fix double-free of card_number_str in Plantain parser

printf_plantain_data() freed purse->card_number_str, then the caller freed the same (non-NULL) pointer again at the end of the parse -> double-free / heap corruption on every Plantain card. Remove the free inside the print helper; the caller that allocated it owns the free.

Part of #1029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: fix double-free + secondary-ticket leak in SZPPK parser

In the second-ticket branch the cleanup freed primary_ticket.* (already freed just above) instead of the freshly-allocated secondary_ticket.*, causing a double-free of the primary strings and a leak of all four secondary strings on two-trip tickets. Free the secondary_ticket.* members.

Part of #1029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: fix double-free + secondary-ticket leak in SEVPPK parser

Same copy-paste bug as SZPPK: the second-ticket branch freed primary_ticket.* (already freed) instead of secondary_ticket.*, double-freeing the primary strings and leaking the secondary ones on two-trip tickets. Free the secondary_ticket.* members.

Part of #1029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: fix double-free + secondary-ticket leak in SK parser

Same copy-paste bug as SZPPK/SEVPPK: the second-ticket branch freed primary_ticket.* (already freed) instead of secondary_ticket.*, double-freeing the primary strings and leaking the secondary ones on two-trip tickets. Free the secondary_ticket.* members.

Part of #1029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: fix restricted_weekday_string leak in Saflok parser

restricted_weekday_string was allocated and only read via furi_string_get_cstr; the file had no furi_string_free at all, leaking one FuriString on every Saflok card parse (both MFC and UL-C variants). Free it before the do/while(false) exits (after #endif so it applies to both SL_PROTO builds).

Part of #1029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: free api_resolver in nfc_app_free (fix per-launch leak)

instance->api_resolver (composite_api_resolver_alloc + two composite_api_resolver_add nodes) was never freed; nfc_supported_cards only borrows it. Leaked the resolver struct plus its two list nodes on every NFC-app open->close. Free it after nfc_supported_cards_free (its only user).

Part of #1029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: fix Stream + storage-record leak on CUID dict open failure

When a CUID dictionary passed keys_dict_check_presence but buffered_file_stream_open then failed, cleanup did only buffered_file_stream_close + free(dict), leaking the Stream object and the RECORD_STORAGE reference opened just above. Use keys_dict_free(dict), which closes+frees the stream, frees the dict, and closes RECORD_STORAGE, matching the total_keys==0 path.

Part of #1029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: fix ~15x RAM over-allocation of DESFire file_data array

mf_desfire_file_data_array_config.type_size used sizeof(MfDesfireData) (~60+ bytes) but the element type is MfDesfireFileData ({ SimpleArray* data; } = 4 bytes), so every file slot over-allocated ~15x, scaled by (files x applications) on the card. Use sizeof(MfDesfireFileData). Consistent stride, so no behavior change beyond reduced heap use.

Part of #1029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* changelog: NFC memory leaks & double-frees (PR #1030, Fixes #1029)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 18:16:46 +03:00
Mykhailo ShevchukandClaude Opus 4.8 eb49a89441 ci: auto-bump issue-template stable version on release publish [ci skip]
Add a workflow that, when a stable `unlshd-*` release is published, rewrites
the bug-report template's "Found in version" dropdown to keep exactly one
stable option (the new release tag) + "dev", then commits it to dev.

- Prerelease/non-unlshd tags are skipped.
- Surgical regex edit (minimal one-line diff), validated with yq before commit.
- Pushes via GITHUB_TOKEN don't retrigger CI; [ci skip] added to match convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 14:40:29 +03:00
Mykhailo ShevchukandClaude Opus 4.8 34c3c9f072 issue templates: use dev build number for Dev version field [ci skip]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 14:10:30 +03:00
Mykhailo ShevchukandClaude Opus 4.8 d9842247ab issue templates: capture firmware version in bug report, fix typos
- Bug report: drop the unused `Target` field; add a required
  "Found in version" dropdown (unlshd-089 / dev) plus an optional
  "Dev version" text input for the exact dev build identifier.
- Fix typos: "a bug reports" -> "a bug report", "whetever" -> "whether".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 14:04:20 +03:00
MX 37c6dd97f5 testing push notifications for qUnleashed app 2026-07-05 03:48:31 +03:00
MX 056d3ea328 upd changelog
hotel parser might conflict with current saflok parser? disabling just in case
2026-07-05 03:20:32 +03:00
MX ad60eeada0 Merge remote-tracking branch 'OFW/dev' into dev 2026-07-05 03:17:03 +03:00
Eric BettsandGitHub 101c20d736 NFC Plugin to identify some hotel systems (#4323) 2026-07-05 01:50:08 +03:00
d4b023ce53 CCID: add interrupt support (#4198)
* CCID: add interrupt support

Allow smartcard to be inserted or removed by calling the following functions:
 - furi_hal_usb_ccid_insert_smartcard
 - furi_hal_usb_ccid_remove_smartcard

These functions will use the interrrupt endpoint to send a notify change message

* Clang format

* Fixes

* fbt format and logs to debug

---------

Co-authored-by: Štefan Croitoru <66339601+turbospok@users.noreply.github.com>
2026-07-05 01:02:44 +03:00
MX 1be39664c9 Merge remote-tracking branch 'OFW/dev' into dev 2026-07-05 00:51:20 +03:00
Louis Habberfield-ShortandGitHub 86d9141bb4 Fix broken docs links (#4405) 2026-07-04 21:31:13 +03:00
5cdf9b3374 ci: post Build & analyze report on fork PRs via workflow_run (#1027)
* ci: post Build & analyze report on fork PRs via workflow_run

The Comment PR report step in pr-build.yml fails with 403 "Resource not
accessible by integration" on fork PRs: GitHub gives `pull_request` runs
from forks a read-only GITHUB_TOKEN (they build untrusted code), so
issues.createComment is forbidden and the whole f7 build check goes red
even though the firmware built fine.

Split delivery from analysis:
- pr-build.yml keeps all build/size/API analysis on the fork's code with
  the read-only token, but instead of commenting it uploads the assembled
  report + PR number as the `pr-report` artifact. Drops pull-requests:write.
- pr-comment.yml (new) is triggered on workflow_run when the build
  completes. It runs from the default branch in the base-repo context, so
  it gets a read-write token even for fork-triggered builds, downloads the
  artifact, and posts/updates the comment. It never checks out or runs PR
  code. The PR number travels via the artifact because
  workflow_run.pull_requests is empty for fork PRs.

Same-repo PRs keep working; fork PRs now get the report comment a few
seconds later, and the build check is no longer falsely red.

Closes #1026

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: harden fork-PR comment workflow after review

Review + simplify pass on the workflow_run split:

- pr-comment.yml: add `actions: read` — an explicit permissions block sets
  unlisted scopes to none, and a cross-run download-artifact needs it; without
  it the download 403s, continue-on-error swallows it, and the comment silently
  never posts. This is the fix that makes the feature actually work on forks.
- pr-comment.yml: skip cancelled/skipped builds in the job `if`, and warn (job
  annotation) when the artifact can't be downloaded, so a genuine failure isn't
  a green check with no comment and no signal.
- pr-comment.yml: add a concurrency group (keyed on the build run id, since
  workflow_run.pull_requests is empty for forks) so a re-run can't race the
  find-or-create into duplicate comments; log incomplete artifact at warning,
  not info; per_page:100 on listComments to cut pagination round-trips.
- pr-build.yml: mark the report upload continue-on-error so a transient upload
  hiccup can't redden an otherwise-green build (the point of the split); fix
  two comment overstatements (push/dispatch summary omits the PR-only API
  section; concurrency also covers pushes to dev).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 19:01:47 +03:00
b8c5858f40 [RFID]: Fix reading T5577 tags that hold multiple EM4100 IDs (#1025)
* [LFRFID]: Fix reading T5577 tags that hold multiple EM4100 IDs

A T5577 configured to emit more than one EM4100 frame per cycle (e.g. two IDs
written by the T5577 Multi Writer app) hung on Read.

The Electra protocol (2024) gave the EM4100 decoder a 64-bit "the frame must be
followed by a header" lookahead to tell EM4100 and Electra apart. That pushed
each decode ~128 bits past the read worker's per-decode reset, so a two-frame
tag locked into rigid A/B alternation and never produced the 3 identical decodes
in a row that Read requires.

Only a 9-bit lookahead is actually needed (Electra's epilogue never begins with
9 ones), so shrink the epilogue register to 9 bits. This restores the
pre-Electra decode timing while keeping EM4100/Electra disambiguation. Also reset
encoded_epilogue in decoder_start, which the original change omitted.

Fixes #1024

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [LFRFID]: Reset encoded_data in PAC/Stanley decoder_start

protocol_pac_stanley_decoder_start reset data/inverted/got_preamble but never
the encoded_data shift buffer - missing since the protocol was added, and unlike
every sibling decoder. Latent in practice (the buffer self-refreshes and
decoding is preamble/parity/checksum-gated), fixed for consistency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [LFRFID]: Changelog for multi-EM4100 T5577 read fix (#1025)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 18:28:11 +03:00
7cff5faae7 [NFC]: Show a loading screen while a large CUID dictionary loads on Read (#1022)
* [NFC]: Show loading spinner while a large CUID dictionary loads on Read

Reading a MIFARE Classic tag that has a per-UID ("CUID") key dictionary
scans the whole mf_classic_dict_<cuid>.nfc up front (to count keys and
build the key-index bitmap) in prepare_view(), which runs from on_enter
before the dictionary-attack progress view is shown. For a large
dictionary the screen stays blank and looks like the app has hung.

Wrap that scan in the existing animated loading popup (the same one used
for the supported-cards cache load), gated on CUID-dictionary presence so
ordinary MF Classic reads are unchanged (no spinner flicker). The CUID
dictionary path is factored into a small shared helper.

Closes #1021

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [NFC]: Label the CUID-dictionary loading screen

Replace the plain centered spinner shown while a per-UID ("CUID")
dictionary loads with a dedicated view that draws the animated spinner on
the left and a "CUID dictionary is loading" label on the right, so it is
clear what the Flipper is doing during the (possibly long) scan.

Implemented as a self-contained NFC view (loading_label) using only
already-exported API symbols, so it needs no firmware API changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: changelog for CUID dictionary loading screen

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 02:21:32 +03:00
bcc05f2bcb ci: auto-label PRs with area/* by changed paths (#1019) [ci skip]
Adds actions/labeler config + workflow that tags each PR with the
relevant area/* label(s) based on the files it touches, so the
subsystem taxonomy stays applied to PRs without manual effort.

- sync-labels is off: labels are only added, never stripped, so
  hand-applied labels are preserved.
- Uses pull_request_target so PRs from forks can be labeled; the
  workflow never checks out or executes PR code.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:59:36 +03:00
ad2f900253 chore: align issue-template labels with new namespaced taxonomy (#1018) [ci skip]
Repo labels were reorganized into type/*, status/*, resolution/* and
area/* namespaces. Update the auto-applied template labels to match:

- bug report      -> type/bug
- feature request -> type/feature
- enhancements    -> type/enhancement (previously applied no label)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:22:27 +03:00
9ef0b565ef [NFC]: Read MIFARE Plus 2K (SL1) as full 32 sectors / 64 keys (#1016)
A MIFARE Plus 2K in Security Level 1 is byte-identical to a Classic 1K
in SAK (0x08) and ATQA (0x0004), so it was mis-sized as 1K and only the
lower half of the card (16 sectors) was read. Tell them apart via the
ISO14443-4 ATS: a genuine Plus answers RATS with a Plus S/X product
signature, a Classic 1K never answers RATS.

- Add MfClassicType2k (32 sectors / 128 blocks / 64 keys)
- Probe RATS in the Classic poller's sak & 0x08 branch; promote to 2K
  only on an exact Plus S/X ATS historical-byte match. Plus SE (1K,
  distinct ATS), SmartMX, magic "Perfect CUID" and plain 1K stay 1K
- Harden the sync detector so the appended enum value can't make every
  card mis-probe as 2K
- Accept 2K in the NDEF supported-card guard
- Fix a pre-existing NfcProtocol[] memcpy size bug in the scanner that
  multi-protocol (Classic + Plus) cards exercise

Closes #917

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 14:40:59 +03:00
MX 3833290ce9 upd apps tag [ci skip]
updated pack was used with previous commit
2026-07-02 20:45:52 +03:00
68952d814a [NFC]: Align MIFARE type detection with AN10833 (Classic SAK sizing, UL/NTAG GetVersion typing, Plus SL0/SL3/EV1/EV2, Ultralight AES) (#1014)
* nfc: size MIFARE Classic from SAK per AN10833

The Classic poller sized cards purely behaviorally (auth-probe block 254
-> 4K, else block 62 -> 1K, else Mini) and ignored the SAK it already
captures at activation. A magic CUID that answers every block was thus
mis-sized as 4K despite its 1K SAK.

Derive the size from the SAK bit map first (AN10833: 0x09 -> Mini, bit4 ->
4K, bit3 -> 1K) and fall back to the behavioral block probe only when the
SAK is not a recognized Classic value. Genuine cards are unchanged (their
SAK already matches their size); clones with a standard SAK are now sized
to what they claim instead of what every-block-answers implies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: report unmodelled GetVersion Ultralight as Unknown, not Origin

mf_ultralight_get_type_by_version defaulted an unrecognized GetVersion
response to MfUltralightTypeOrigin. But answering GetVersion at all proves
the IC is not an original Ultralight (which has no GetVersion), so a modern
card such as Ultralight AES was silently mislabelled a 16-page original.

Add MfUltralightTypeUnknown (conservative, Origin-identical features; not
offered for Write since the app's allow-list omits it) and default to it.
Reserves Origin for the no-GetVersion fallback path. The NTAG212/UL21
storage-size collision and full support for new product types are left for
a follow-up that adds those types with correct page counts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: derive MIFARE Plus security level from SAK bit map (AN10833)

The Plus version path inferred the security level from a binary SAK==0x20
test (SL3, else SL1), which collapsed SL2 entirely and mislabelled genuine
SL2 cards as SL1. Use the SAK bit map instead: 0x20 -> SL3, 0x10/0x11 ->
SL2, 0x08/0x18 -> SL1, anything else -> Unknown rather than a guessed SL1.

GetVersion stays authoritative for type and size; the SAK only refines the
level. The no-GetVersion ISO4 fallback (which still relies on ATS/ATQA,
against AN10833) is intentionally untouched here and left for a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: identify Ultralight/NTAG by GetVersion product family (AN10833)

get_type_by_version keyed on storage_size alone, so ICs that share a
storage size across families were mistyped: NTAG212 read as UL21, NTAG210
fell through to a false original, and Ultralight AES (storage 0x0F) was
mislabelled NTAG213. Gate on the GetVersion product-family nibble (0x03
Ultralight, 0x04 NTAG) first, then size within the family by storage_size.
NTAG I2C stays anchored on subtype/major since its nibble is unreliable
(real silicon 0x07 vs the in-tree generator 0x04).

Add NTAG210 (= UL11 layout), NTAG212 (= UL21 layout), and Ultralight AES
(detect/label only -- AES auth is not implemented, with a conservative
read-only page count pending datasheet validation). NTAG210/212 are also
added to the NDEF parser allow-list and NTAG212 to the listener dynamic-
lock granularity. The new types are read-only (omitted from the write
allow-list, so the write-end-page assert is never reached).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: drop forbidden ATQA/ATS over-claims in MIFARE Plus iso4 type-ID

mf_plus_get_type_from_iso4 asserted "Plus X" from a bare SAK 0x10/0x11 and
sized SL3 cards from the ATQA nibble -- both signals AN10833 says never to
use for type identification. Report SAK 0x10/0x11 as a generic Plus 2K/4K
SL2 (drop the unjustified X), and leave SL3 size Unknown instead of reading
ATQA.

The ATS-historical-byte match is kept as the SL3 detection gate on purpose:
SAK 0x20 is shared with DESFire and MfPlus is probed before MfDesfire, so
claiming Plus on a bare SAK would hijack DESFire detection. Dropping the
ATQA gate also means an SL3 Plus with a non-standard ATQA is now detected
(size Unknown) instead of missed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: never report an Ultralight AES read as complete

The UltralightAES feature set omits PasswordAuth, so mf_ultralight_is_all_data_read
returned true once the (conservative, unverified) page count was read -- silently
presenting a truncated dump as complete, even though the IC's memory is protected by
AES authentication that the poller does not perform. Treat AES as never-complete so
the read honestly surfaces as partial. Found in PR review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: tidy MIFARE type-id comments and hoist a local (no behavior change)

Trim the verbose AN10833/SL3/Unknown/AES comments to a single "why" line each
per the project's concise comment style, and hoist the repeated SAK deref in
mf_plus_get_type_from_version to a local. Pure cleanup from the simplify pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: type Ultralight/NTAG by storage_size, family nibble only for collisions

The family-nibble gate from the previous commit was too strict: a card whose
GetVersion prod_type low-nibble is neither 0x03 nor 0x04 (common on magic/clone
Ultralight cards) fell through to MfUltralightTypeUnknown, whose config_page is 0
-- so PWD/PACK stopped rendering and the poller skipped PWD auth. The legacy dev
logic keyed purely on storage_size and typed such cards as UL11/UL21.

Restore storage_size-primary typing and use the product-family nibble only to
disambiguate the storage values that collide across families (NTAG210/UL11 0x0B,
NTAG212/UL21 0x0E, NTAG213/Ultralight AES 0x0F). Keeps the three collision fixes
without regressing odd-nibble clones; Unknown now only for an unrecognized
storage_size.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nfc: probe MIFARE Plus SL0 vs SL3 actively (AN10833/PM3 hf mfp info)

SAK 0x20 + ATS is shared by a Plus in SL0, a Plus in SL3, and a DESFire,
so it cannot be classified passively. Detection previously hard-coded
SAK 0x20 -> SL3, so a card in SL0 (personalization state) was reported as
SL3, and any SL3 Plus whose ATS was not in the S/X/SE table fell to
Unknown.

Mirror PM3: after activation, send WritePerso (0xA8) to the intentionally
invalid block 0x9090 and classify the reply -- 0x09 means SL0 (block
rejected, nothing written), 67 00 / 1C 83 0C / 6D 00 mean DESFire or
unsupported (not Plus), anything else is SL3. The probe runs only for
SAK 0x20 and feeds the resolved level into both the GetVersion and ISO-4
type paths.

Because the probe positively rules out DESFire, the ISO-4 SAK-0x20 path
can now report a generic Mifare Plus for an untabled/short ATS instead of
Unknown. When the probe yields no usable result the old strict ATS-table
gate is kept, so DESFire is never hijacked. 2K vs 4K still needs the
AN10833-forbidden ATQA nibble, so SL3 size stays Unknown.

Builds clean; not yet validated on real Plus hardware (SL0 + SL3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: harden MIFARE Plus SL probe (PR-review fixes)

Address review findings on the SL0/SL3 WritePerso probe:

- Classify the reply by an allowlist, not a denylist. Plus answers 0x09
  (SL0) or 0x06/0x0B (SL3); every other reply becomes NotPlus. This fixes
  two issues at once: (1) a single-byte 0x09 SL0 reply was misread as SL3
  due to a spurious "len > 1" guard, defeating SL0 detection; (2) the old
  "anything else maps to SL3" default could promote an unanticipated
  DESFire reply to a confident Plus and hijack DESFire in the ISO-4 path.
  No real DESFire native status code is 0x06/0x09/0x0B, so unknown replies
  now fall through DESFire-safe. Also removes the fragile multi-byte
  denylist.

- Version path: only log "(probe)" when the probe actually resolved the
  level; the SL3 fallback now logs "(default)" instead of claiming the
  probe ran.

- Log the raw WritePerso status byte (FURI_LOG_D) to aid the pending
  on-hardware validation.

- Initialize the probe result local defensively (fail-safe NotPlus).

- Rename the converter to mf_plus_poller_resolve_sak20_security_level to
  distinguish it from the probe, and trim duplicated comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: simplify MIFARE Plus SL probe (post-/simplify)

Quality cleanups from the /simplify pass, no detection-behavior change:

- Extract mf_plus_type_from_ats() and use it at both SAK-0x20 sites,
  removing the duplicated ATS-historical-bytes -> S/X/SE memcmp ladder
  that the probe change had introduced a second copy of.

- Skip the active probe on the detect-phase version path. There the SL is
  written to a throwaway detect poller and discarded, and DESFire is
  already excluded by the GetVersion family nibble, so the probe could not
  change the detect result -- only waste one WritePerso RF round-trip per
  EV-Plus/DESFire scan cycle. The iso4 fallback still probes, since that is
  where the probe provides real detection coverage.

- Route the probe through nxp_native_command_iso14443_4a_poller (the same
  plain-mode path read_version uses) and read its status_code out-param,
  instead of hand-rolling iso14443_4a_poller_send_block + manual byte-0
  extraction. Behaviour-equivalent (an empty reply yields LENGTH_ERROR ->
  NotPlus, as before).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: derive MIFARE Plus SL0/SL3 size from ATQA

SL0/SL3 cards expose no product-independent storage byte, so size showed
Unknown for any Plus reaching the ISO-4 / probe path (GetVersion absent or
hw_storage unrecognized). Once the active probe has confirmed the card is a
Plus, fall back to the ATQA size coding -- bit 2 (0x0004) = 2K, bit 1
(0x0002) = 4K -- the same signal PM3 hf mfp info uses.

This is size refinement of an already-confirmed Plus, not ATQA-based type
identification, so AN10833's caution against ATQA for product ID does not
apply. SE stays 1K regardless; a recognized GetVersion hw_storage still
wins, with ATQA only as the fallback when it is Unknown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: gate Ultralight detection on SAK/ATQA, not a bare read

mf_ultralight_poller_detect only sent a READ page 0 (0x30) and treated any
reply as Ultralight. A magic/Chinese MIFARE Classic clone (gen1a/gen2)
answers an unauthenticated READ 0x30 through its backdoor exactly like an
Ultralight, so such a card was offered to the user as BOTH MIFARE Classic
(correct -- e.g. a Mini) and NTAG/Ultralight (false positive). A genuine
Classic NAKs the unauthenticated read, which is why only clones misfired.

Gate the probe read behind mf_ultralight_detect_protocol() (SAK 0x00 +
ATQA 0x0044) so only cards advertising the Ultralight/NTAG SAK/ATQA are
read as Ultralight. This mirrors PM3, which routes SAK 0x00 to the
Ultralight path and aborts UL detection when atqa[1] != 0x00 || sak != 0x00
before ever issuing READ 0x30. The Chinese Mini clone (SAK 0x09, ATQA
0x0004) now fails the gate and is detected as Classic/Mini only; genuine
and magic Ultralight/NTAG (SAK 0x00, ATQA 0x0044) are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: loosen Ultralight detect gate to PM3's SAK/ATQA (PR-review fix)

mf_ultralight_detect_protocol required ATQA exactly 0x0044 (atqa[0]==0x44),
but that low byte encodes UID size (0x44 = 7-byte, 0x04 = 4-byte), so the
strict term silently excluded a 4-byte-UID Ultralight/NTAG -- e.g. a magic
UL configured with a 4-byte UID (ATQA 0x0004) -- which the previous bare
read would have detected. The strict term also contributes nothing to the
magic-Classic-clone fix it shipped with: those carry a non-zero Classic SAK
(Mini 0x09, 1K 0x08, 4K 0x18) and are already rejected by the SAK term.

Gate on atqa[1]==0x00 && sak==0x00 only, matching PM3 (which rejects solely
when atqa[1] != 0x00 || sak != 0x00). Magic Classic clones still fail on SAK;
4-byte-UID UL/NTAG are no longer wrongly dropped. Also trims the detect
comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: collapse mf_ultralight_detect_protocol to a direct return (/simplify)

Drop the single-use mfu_detected temporary; the comment already documents
the expression. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: detect MIFARE Plus EV1/EV2 via GetVersion (trust valid reply)

A MIFARE Plus EV1/EV2 answers GetVersion (0x60) with the full, correct
28-byte version block but terminates the native frame exchange with a
status byte other than 0x00 (unlike DESFire), so mf_plus_poller_send_chunks
reported MfPlusErrorProtocol and read_version discarded a perfectly valid
version. The card then fell back to ATS-based typing, which mis-identified
every EV1/EV2 as the older "Plus X" -- EV1/EV2 share the Plus-X ATS
historical bytes, so GetVersion is the only way to tell them apart. The
EV1/EV2 typing itself (hw_major 0x11 -> EV1, 0x22 -> EV2; storage 0x16 ->
2K, 0x18 -> 4K) was already correct but never reached.

Trust the assembled version whenever it parses as a genuine NXP Plus
(vendor 0x04, family nibble 0x02), regardless of the terminal status;
otherwise propagate the real error so older Plus S/X/SE (which do not
answer GetVersion) still fall back to ATS. Reset the result buffer before
the exchange so a first-frame transport failure can't leave stale bytes
that the payload-trust check would accept (nxp_native_command only clears
it after its first frame succeeds).

HW-validated: a Plus EV1 4K in SL0 now reads as "MIFARE Plus EV1 4K SL0"
(was "Plus X 4K SL0"). DESFire (family nibble 0x01) and older Plus S/X/SE
are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: handle MIFARE Ultralight AES as identity-only (no read hang)

A MIFARE Ultralight AES (MF0AES20) needs AES-128 authentication for every
page and returns a 48-byte signature (our reader expects 32), neither of
which the firmware implements. The read poller therefore failed at the
signature/page reads -> ReadFailed, and the app's Ultralight read callback
retries on failure, so the poller restarted from Idle endlessly and the read
froze on "Don't move" forever (confirmed by an on-device poller state trace).

Treat UL-AES as identity-only, like MIFARE Plus (which reads its version/ATS
and stops without dumping memory):

- Poller: GetFeatureSet routes UltralightAES straight to ReadSuccess after
  GetVersion, skipping the AES-gated signature/page reads that can never
  succeed. The card is identified and the read terminates.

- Render: show UL-AES like MIFARE Plus -- Tech + UID (+ ATQA/SAK in Full) --
  instead of "Pages Read: 0/40" / "Password-protected pages!" / counters. The
  Short (read-result) form of iso14443_3a_info omits the Tech line, so it is
  added explicitly to match the Plus result screen.

- Per-card features: add an optional get_features() hook to
  NfcProtocolSupportBase (overrides the static .features bitmask when set) and
  have mf_ultralight report EmulateUid (no MoreInfo/Write/EmulateFull) for
  UL-AES. This drives the menu label ("Emulate UID"), the emulate screen
  ("Emulating UID"), and the now-hidden empty page-dump "More" view from one
  place instead of scattered scene hacks.

- Emulate: UL-AES emulates UID only (Iso14443_3a listener) -- a full emulation
  would be an empty card (no readable memory, no AES-auth listener).

- Menu: don't offer "Unlock" for UL-AES; only the unimplemented AES auth could
  unlock it, so the password-based flow can't help.

No full UL-AES read/emulate (that needs UL-AES AES-128 auth, a separate
feature). Other UL/NTAG types are unaffected: get_features returns the full
set, and every other protocol keeps its static features. HW-validated: no
more hang; result screen and menu match MIFARE Plus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* nfc: name MIFARE Mini per NXP product name (drop "Classic")

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(changelog): AN10833 type-detection alignment; drop fixed Mini-clone regression

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: MX <10697207+xMasterX@users.noreply.github.com>
2026-07-02 20:25:45 +03:00
MX 6c8dcb3f9a Merge remote-tracking branch 'OFW/dev' into dev [ci skip] 2026-07-02 16:18:17 +03:00
Evgeny EandGitHub 05792f1a91 Storage: add retry on file copy to Flipper (#4402)
* Updater test: fail fast on hung copy + reboot-retry on bench

storage.py waits for timeout during a file copy
updater_test.yml splitted and added retry loop in a build-once / try -> hard-reset -> retry.

* Updater test: restore 5-minute step timeouts
2026-07-02 16:16:19 +03:00
Ruslan NadyrshinandGitHub 8c653cce54 Fixed side menu in the FZ dev docs (#4403) 2026-07-01 15:16:55 +03:00
MX 0d123bdfa5 Merge remote-tracking branch 'OFW/dev' into dev 2026-07-01 01:50:39 +03:00
Àlex SánchezandGitHub ffc1280a2c Fix USB HID keyboard LED state reporting (#4162) (#4338)
Add missing OUT endpoint to USB HID keyboard interface so the host
can send LED state reports (CapsLock, NumLock, ScrollLock) to the device.
This makes furi_hal_hid_get_led_state() return the actual host LED state
instead of always returning 0.
2026-07-01 01:39:38 +03:00
0dd3681f63 NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (#4369)
mf_ultralight_listener.c: FAST_READ handler wrote page_cnt*4 bytes into
a fixed pages[64] stack buffer without checking page_cnt <= 64. For tags
with pages_total > 64 (NTAG215: 135, NTAG216: 231) a reader could trigger
an overflow of up to 668 bytes. Fix: reject requests where page_cnt
exceeds COUNT_OF(pages) with NAK.

mf_desfire_i.c: additional_access_rights_len (a count) was validated
against MF_DESFIRE_MAX_KEYS * sizeof(MfDesfireFileAccessRights) (= 28)
instead of MF_DESFIRE_MAX_KEYS - 1 (= 13), allowing a rogue tag to write
up to 56 bytes into a 26-byte stack slot. Fix: compare count against
MF_DESFIRE_MAX_KEYS - 1.

Co-authored-by: Leke Hapciu <imudheash@gmail.com>
2026-07-01 01:24:18 +03:00
MMXandGitHub de0e3cf3b1 Fix condition for cleaning up dict attack resources (#4400) 2026-07-01 01:09:50 +03:00
loftyandGitHub ed0c63d8a9 Make view_port_send_to_back public (#4320)
`view_dispatcher_send_to_back` and `view_holder_send_to_back` are
public, as `view_port_send_to_front`.
2026-07-01 01:01:19 +03:00
ApertureFox TechnologyandGitHub ec6d0c906f GUI: add canvas_buffer to public api (#4399)
* GUI: add canvas_buffer to public api

* GUI: add canvas_buffer to public api (f18)
2026-07-01 00:56:36 +03:00
Artem KrylysovandGitHub 093d77e5b7 NFC: Fix BusFault in Write to Initial Card (#4362)
mode_ctx is a union where write_ctx.tag_block overlaps with dict_attack_ctx.nested_nonce.nonces. In "Write to Initial Card" it causes mf_classic_poller_free calling free on a garbage pointer
2026-07-01 00:34:09 +03:00
BenjaminandGitHub 8bdf46beed Address utf8 support when uploading JavaScript application (#4364) 2026-07-01 00:01:47 +03:00
f536d3e503 SEN recovery optimizations (#4337)
* SEN recovery optimizations

* fbt format

---------

Co-authored-by: Štefan Croitoru <66339601+turbospok@users.noreply.github.com>
2026-06-30 23:54:40 +03:00
842b8a4b09 NFC: ISO 15693-3 emulation READ_MULTI and GET_BLOCK_SECURITY fixes (#4334)
Co-authored-by: Aaron Tulino <git@aaronjamt.com>
2026-06-30 23:33:41 +03:00
WillyJLandGitHub 0682ef3de8 NFC: Fix sending 32+ byte ISO 15693-3 commands (#4333) 2026-06-30 23:02:30 +03:00
Alexey ZakharovandGitHub 225d416b44 Update js_gui.md to reflect firmware 1.4 changes and fix a typo in the js_notification.md (#4329) 2026-06-30 22:21:10 +03:00