Signature bypass
Description
verify_message
incorrectly assumes that the layout of the calldata passed to the VerifyMessage
instruction is as follows:
Offset Size Handler argument name/Significance
0 8 ANCHOR HEADER
8 4 message_data length (4+64+32+2+psz)
12 message_data
12 4 SOLANA_FORMAT_MAGIC
16 64 signature
80 32 pubkey
112 2 payload size (psz)
114 psz payload
114+psz 2 ed25519_instruction_index
116+psz 1 signature_index
117+psz 2 message_offset
Under this assumption, message_offset
has a value of 12, referencing the offset of the data of the message_data
vector in the raw encoding of the calldata for the VerifyMessage
instruction.
The verify_message
function performs several checks on the data contained in message_data
using offsets computed assuming the storage layout outlined above.
However, if the calldata layout and message_offset
are adjusted in the following way:
Offset Size Handler argument name/Significance
0 8 ANCHOR HEADER
8 4 message_data length (4+64+32+2+psz)
12 message_data
12 4 SOLANA_FORMAT_MAGIC
16 64 <UNUSED>
80 32 PK1
112 2 payload1 size (psz1)
114 psz1 payload1
114+psz1 4 <UNUSED>
118+psz1 64 valid signature for PK2
182+psz1 32 PK2
214+psz1 2 payload2 size (psz2)
216+psz1 psz payload2
216+psz1+psz2 2 ed25519_instruction_index
218+psz1+psz2 1 signature_index
219+psz1+psz2 2 message_offset (114+psz1)
The verify_message
function will read the public key, payload size and payload appearing earlier in the calldata, while the ec25519 instruction will reference the latter instances.
Thus, verify_message
will incorrectly check that the PK1 public key appears in the list of trusted signers, while the ec25519 instruction will require a signature from a different PK2 public key. Additionally, the signed payload and the payload returned by verify_message
also differ arbitrarily both in their content and their size.
Impact
If exploited, this issue allows to completely bypass signature verification of a price feed update, allowing to forge a price feed update with an arbitrary payload and that appears to originate from any address in the set of trusted signers.
The exploitability of this issue is conditional on the program invoking the VerifyMessage
instruction. The attacker needs to have control over the message_offset
provided to VerifyMessage
. The official integration examples provided by Pyth Data Association did not allow the end user to specify the offset provided to the VerifyMessage
instruction. These preconditions lower the likelyhood of the vulnerability. However, Lazer integrations are not required to use the examples provided by Pyth Data Association, and there seems to be some more advanced and reasonable scenarios where a program integrating with Lazer could give an attacker control over the offset argument.
Recommendations
It appears to be possible to entirely remove the message_offset
argument, fixing its value to 12, which would constrain the data referenced by the ec25519 program to be aligned with the start of the message_data
argument of the VerifyMessage
instruction. Note that the offset used by the ec25519 program would still need to be checked to be 12 if removing the message_offset
argument.
Remediation
This issue has been acknowledged by Pyth Data Association, and a fix was implemented in PR #2250↗.
The PR added a check requiring the message_data
processed by the verify_message
function to match the data considered by the ec25519 program.
Additionally, the message_offset
argument indicating the start of the message data processed by the ec25519 program was removed.
The code could not be simplified further by requiring a stricter layout for the data processed by the ec25519 program, as Pyth Data Association indicated this flexibility is a requirement.