Skip to content

Conversation

@henrygab
Copy link
Contributor

Generally, some bug fixes, and some syntactic sugar (with minimal or no impact to compiled binary).

  • FIX: encoding of interval (aka expiration date) was incorrect
  • FIX: const correctness allows more compiler optimizations.
  • Rather than uint8_t * of unknown length, functions now take pointers to structures of the appropriate size.
    • Makes it easier to properly use the functions, as they self-document not only the type, but how many bytes might be accessed through that pointer.
    • Especially useful when code is later maintained by someone other than original author.
    • May improve some static analysis tools outputs.
  • Wrappers for property getters / setters
    • Allows easier single-point-of-correction.
    • Example: When eltrick determined that the card creation year field is only 7-bits, the fix was localized.
  • Very basic self-test option added.
    • Framework makes it trivial to add more test cases later.
  • Bit set / get routines now reject invalid values
    • Values must fit within number of bits
    • Warning/error messages are output
    • Yes, this helped catch code errors
  • Adding missing output for cards that do not have variable keys.
    • No, I don't know what "variable keys" means ... I just fixed the output.

This should have zero effect on an optimized build,
as there are effectively zero changes to the instructions.

Rather, this provides greater clarity as to the size of buffers
being passed to various functions, which improves the
long-term maintainability of code, especially when
maintained by someone other than the original author.
…utines.

Again, these should have zero impact on resulting
instructions.   At the same time, this will enable
static analysis to more accurately validate a buffer
(here `saflock_mfc_data_t`)  points to sufficiently
large allocated memory.

Additional validation for `extract_bits()` and
`insert_bits()` will prevent accidental corruptions
(and assert, if assertions are enabled).

These changes also makes the code easier to follow.
…ok formats

* Small, testable functions
* `set_` functions return boolean so they can indicate out-of-bounds values and similar failures.
* const-correctness for all helper functions

Note: The new functions are NOT used extensively yet ... they still need testing.   But they should make the code more self-documenting / readable (if somewhat verbose).
run `hf saflok decode` twice ... and it will run old decoder one time, and new decoder the other time.   Makes it easy to compare side-by-side in this interrim build.
@github-actions
Copy link

You are welcome to add an entry to the CHANGELOG.md as well

@henrygab henrygab marked this pull request as ready for review November 17, 2025 10:07
Copilot AI review requested due to automatic review settings November 17, 2025 10:07
Copilot finished reviewing on behalf of henrygab November 17, 2025 10:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the Saflok MIFARE Classic card handling code with a focus on type safety, const-correctness, and bug fixes. The changes introduce strongly-typed structures, comprehensive getter/setter functions for bitfields, and improved datetime handling.

Key Changes:

  • Replaced raw byte arrays with typed structures (saflok_mfc_data_t, saflok_mfc_key_t, etc.) for better type safety and self-documentation
  • Added comprehensive getter/setter wrappers with range validation for all card data fields
  • Fixed interval (expiration date) encoding and added datetime manipulation functions
  • Introduced a basic self-test framework (hf saflok selftest command)

Reviewed Changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
doc/commands.md Added hf saflok selftest command documentation; removed waveshare section
doc/commands.json Updated command descriptions and added selftest command entry; removed waveshare commands
client/src/pm3line_vocabulary.h Added selftest to vocabulary; changed bambukeys to keygen; removed waveshare commands
client/src/cmdhfsaflok.c Major refactor: added typed structures, getter/setter functions, datetime handling, bounds checking, and self-test framework
client/src/cmdhfmf.c Minor whitespace/formatting fix
client/src/cmdhf14b.c Minor whitespace/formatting fixes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Discovered by github copilot.
Verified actual bugs by a human.
Fixed by a human.
@henrygab henrygab marked this pull request as draft November 17, 2025 10:44
@iceman1001
Copy link
Collaborator

Interesting, just after a quick look I can see some issue.

  1. make style, your setup doesn't have waveshare meaning we loose some commands now. Please revert.
  2. uid can be 4,7,10 there is nothing saying a MFC for saflok can't be a UID 7 bytes.

Some people love structs, as long as they fit all the scenarios it's ok. We tend to use uid[10] and uidlen in the struct to keep track of things.

  1. assert, as long as assert doesn't "exit the client" when triggered its ok, but if not the client isn't handled in a nice way. Then you have to redo the concept.

@henrygab
Copy link
Contributor Author

henrygab commented Nov 17, 2025

  1. make style, your setup doesn't have waveshare meaning we loose some commands now. Please revert.

Interesting... I cannot both test my changes and easily revert this, but as it's automated, perhaps you could simply run the automated command after the merge? I have no idea why the waveshare command isn't available for my builds. If you know why, I'd appreciate if you could share the secret!

  1. uid can be 4,7,10 there is nothing saying a MFC for saflok can't be a UID 7 bytes.

While the MFC UID might be longer, the algorithm as used in the code I edited was limiting the UID to 4 bytes. This code replicated the original behavior, and I do not have a MFC Saflok with 7 or 10 byte UID to test/validate such an algorithmic change.

Some people love structs, as long as they fit all the scenarios it's ok. We tend to use uid[10] and uidlen in the struct to keep track of things.

The saflok code that was checked in used only the first four bytes of the UID (even though it read more bytes from the card). I did not change the logic When this command is expanded to support >4 bytes of the UID (or UL-C, or any of a number of other extensions), adding an explicit length field for the UID makes total sense and absolutely should be done.

  1. assert, as long as assert doesn't "exit the client" when triggered its ok, but if not the client isn't handled in a nice way. Then you have to redo the concept.

There are were only two assert() in the code. Yes, this exits the client. These two asserts are for programming errors (not user / data errors). There is no other option in the current code's architecture to prevent either silent data loss, silent data corruption, or silently returning bad data to the caller ... which is likely to be data corruption or data loss.


Are you sure you don't want *_any_* asserts?

I agree that user errors should not be assertions.
I also agree that receiving corrupt or unexpected data from a card should not cause an assertion.

At the same time, asserts catch coder/programmer errors. Unfortunately, much of the code does not have proper error handling ... or explicitly ignores error return values. Thus, there is often no way to return an error cleanly from a deep stack, because intermediate functions will blindly ignore the error and continue, even when something went wrong.

Prior to adding the validation, some of the behavior was (iirc) silent data corruption. If lucky, this would crash the client. If unlucky, it could corrupt the data on the card. The silent nature of that data corruption is not something I think should be left to occur.

Therefore, what behavior would you suggest for the following situation:

  • the error is a code bug (e.g., setting values that don't fit in the bitfield length)
  • the error in the original code base would not be handled/propagated correctly
  • the error in the original code base might silently corrupt data

In the end, you have final choice to accept or reject the PR, and can make changes to the PR (before or after merging). In other words... you have absolute choice...


@iceman1001
Copy link
Collaborator

Take my opinions with the perspective of having a nice user experience. The client randomly exiting is not a nice user experience.
When an error is detected, clean up as much as we can after it and let the user enter new commands in the CLI. As robust as we can.

@henrygab
Copy link
Contributor Author

I'm open to you changing the asserts in the saflok file. Of course, there are other assert() in the client code that should also be changed, if this is the goal:

  • client/src/pm3_luawrap.c
  • client/src/imgutils.c
  • client/src/py3_pywrap.c
  • client/src/fido/cbortools.c
  • common/lz4/lz4.c
  • ...

I'm off to bed. I've fixed enough issues (esp. around interval / expiration) that I recommend merging, and then changing whatever remains that you don't like.

@henrygab henrygab requested a review from Copilot November 17, 2025 18:00
Copilot finished reviewing on behalf of henrygab November 17, 2025 18:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 4 out of 6 changed files in this pull request and generated 12 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@henrygab
Copy link
Contributor Author

And ... I've added some regressions in date handling.

@henrygab
Copy link
Contributor Author

@iceman1001 -- Here's the link to the KDF algorithm prior to my changes. Note it only uses / supports a 4-byte UID.

https://github.com/STIEBELJOSHUA/proxmark3/blob/15026f7b3506b75b47831d75fa66bc25efc5ca2e/client/src/cmdhfsaflok.c#L282-L297

I simply wanted you to see that I wasn't trying to remove support for other UID sizes ... the algorithm simply didn't use / support anything else, and I don't have the knowledge on changes that would be needed for other UID sizes (or any ability to test them).

@henrygab
Copy link
Contributor Author

@iceman1001 -- asserts removed, manually tested both valid and invalid edge cases for expiration dates. There's still differences between hf mf info and hf saflok read for certain expiration dates, but I believe the bug lies in hf mf info.
Pending completed CI checks, this is ready.

@henrygab henrygab marked this pull request as ready for review November 17, 2025 23:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants