CAT1 Vulnerability Explained – CVE and CWE

-

Part of our commitment to the security of the Chia blockchain and community is providing transparency around security and the lessons learned when a security incident is discovered. In this spirit, we will have a Common Vulnerabilities and Exposure (CVE) and Common Weakness Enumeration (CWE) of the recent CAT1 vulnerability published Soon™.

In the meantime, we will provide technical details of the CAT1 vulnerability here. For a more general overview, please refer to our previously published blog post, Upgrading the CAT Standard.

CVE – Description of the Vulnerability

The CAT1 standard calculates the coin ID of the CAT1 coin being spent by taking the SHA 256 hash of the concatenated parent coin ID, CAT1 coin puzzle hash, and CAT1 coin amount, as shown in this example:

(stager
  (list MOD_HASH (sha256 ONE MOD_HASH) TAIL_PROGRAM_HASH)
  (a INNER_PUZZLE inner_puzzle_solution)
  lineage_proof
  (sha256tree1 INNER_PUZZLE)
  ;; calculate coin ID
  (sha256 (f this_coin_info) (f (r this_coin_info)) (f (r (r this_coin_info))))
  prev_coin_id    ; ID
  this_coin_info  ; (parent_id puzzle_hash amount)
  next_coin_proof ; (parent_id innerpuzhash amount)
  prev_subtotal
  extra_delta
)

However, this method only ensures that the full set of concatenated bytes is correct and not that the constituting components are correct. If bytes are moved from the end of the CAT1 coin puzzle hash to the beginning of the amount, the same SHA 256 hash will be computed. This enables an incorrect amount to be used, resulting in a larger-sized coin being created. To prevent this, coin IDs must be checked to ensure that constituent components are correct.

(defun calculate_coin_id (parent puzzlehash amount)
  (if (all (size_b32 parent) (size_b32 puzzlehash) (> amount -1))
    (sha256 parent puzzlehash amount)
    (x)
  )
)

(stager
  (list MOD_HASH (sha256 ONE MOD_HASH) TAIL_PROGRAM_HASH)
  (a INNER_PUZZLE inner_puzzle_solution)
  lineage_proof
  (sha256tree INNER_PUZZLE)
  ;; calculate coin ID securely
  (calculate_coin_id (f this_coin_info) (f (r this_coin_info)) (f (r (r this_coin_info))))
  prev_coin_id    ; ID
  this_coin_info  ; (parent_id puzzle_hash amount)
  next_coin_proof ; (parent_id innerpuzhash amount)
  prev_subtotal
  extra_delta
)

The CAT1 vulnerability is a part of a class of vulnerabilities that can arise from insecure concatenation. Concatenation is commonly combined with hashing, where bytes are concatenated to form a preimage and passed to a hash function. To avoid this class of vulnerability, each component must be checked for correctness before concatenation. An example of this is provided in the calculate_coin_id function, where the parent coin ID and puzzle hash are checked for the correct size of 32 bytes, and the amount is checked to be non-negative.

CWE – How to Recreate the Vulnerability

A coin is represented by a variable length structure of the form (parent_coin_id puzzle_hash amount) where the first two fields are 32 bytes, and the last is a clvm varint type.

  • varint examples:
    • 0 = "" (the empty string)
    • 1 = 0x01
    • 50 = 0x32
    • 20000 = 0x4e20
    • 65000 = 0x00fde8 because a varint supports negative values, we prefix numbers that would otherwise have the high bit set with a 00 byte

The solution to a CAT1 puzzle takes a triple of three values for the coin the puzzle is included in. It adds a condition (ASSERT_MY_ID v) where v is the calculated coin id using these values via (sha256 parent_coin_id puzzle_hash amount). It further uses the amount value to ensure mojos are neither created nor burned (except when authorized by the tail).

The problem is that sha256 concatenates the passed values when evaluating, and at no time do we ensure that parent_coin_id or puzzle_hash is 32 bytes as expected (or verified to be correct by any other means). That means the boundary between puzzle_hash and amount can be varied, changing the perceived amount of the coin.

So slide over the last byte or two of the puzzle_hash to be the new first byte or two of amount, and you’ve just created a coin worth much more (or, if the first byte has the high bit set, a negative amount!).

This also gives us an easy way to melt CAT coins: move the boundary all the way to the right, so the puzzle_hash field contains all the bytes from the actual puzzle_hash field and the amount field, so the amount field appears to be the empty string, which represents 0.


The CVE for this vulnerability is published on the Mitre CVE website and can be found here: CVE-2022-36447

Security is a critical component of our work, and we maintain a proactive approach by regularly commissioning external audits of Chia code. The discovery of this vulnerability by Trail of Bits is from our third independent audit of Chia code, and a report will be published on their findings. Previous audit reports can be viewed here:

We understand this was a big change on a short timeline. We appreciate the collaboration of the CAT issuers and community developers to roll these changes out efficiently, and we’re especially grateful for the community’s patience through the process.