You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: lints/type_cosplay/README.md
+60-2Lines changed: 60 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -34,14 +34,72 @@ or u8, and not an enum. This will flag a false positive.
34
34
35
35
## Note on Tests
36
36
37
-
**insecure-anchor**: insecure because `User` type derives Discriminator trait (via `#[account]`),
37
+
### insecure
38
+
39
+
This is the canonical example of type-cosplay. The program tries to deserialize
40
+
bytes from `AccountInfo.data` into the `User` type. However, a malicious user could pass in
41
+
an account that has in it's data field the `Metadata` type. This type is equivalent to the
42
+
`User` type, and the data bytes will thus successfully deserialize as a `User` type. The
43
+
program performs no checks whatsoever, and will continue on operating with a pubkey that it
44
+
believes to be a `User` pubkey, not a `Metadata` pubkey.
45
+
46
+
### insecure-2
47
+
48
+
This is insecure because the program tries to deserialize from multiple enum types.
49
+
Here, `UserInfo` and `MetadataInfo` enums are both being deserialized. Note that both of these
50
+
enums contain a single variant, with the struct type nested inside it. This evades the in-built
51
+
discriminant of an enum. A `Metadata` type could be deserialized into a `UserInfo::User(User)`,
52
+
and a `User` could be deserialized into a `MetadataInfo::Metadata(Metadata)`.
53
+
54
+
Only deserializing from a single enum is safe since enums contain a natural, in-built discriminator.
55
+
If _all_ types are nested under a variant of this enum, then when deserializing, the enum variant
56
+
must be matched first, thus guaranteeing differentiation between types.
57
+
58
+
However, deserializing from multiple enums partitions the "set of types" and is thus not exhaustive
59
+
in discriminating between all types. If multiple enums are used to encompass the types, there may
60
+
be two equivalent types that are variants under different enums, as seen in this example.
61
+
62
+
### insecure-3
63
+
64
+
This example is insecure because `AccountWithDiscriminant` could be deserialized as a
65
+
`User`, if the variant is `Extra(Extra)`. The first byte would be 0, to indicate the discriminant
66
+
in both cases, and the next 32 bytes would be the pubkey. The problem here is similar to
67
+
the insecure-2 example--not all types are nested under a single enum type. Except here,
68
+
instead of using another enum, the program also tries to deserialize `User`.
69
+
70
+
This illustrates that in order to properly take advantage of the enums natural built-in
71
+
discriminator, you must nest _all_ types in your program as variants of this enum, and
72
+
only serialize and deserialize this enum type.
73
+
74
+
### insecure-anchor
75
+
76
+
Insecure because `User` type derives Discriminator trait (via `#[account]`),
38
77
thus one may expect this code to be secure. However, the program tries to deserialize with
39
78
`try_from_slice`, the default borsh deserialization method, which does _not_ check for the
40
79
discriminator. Thus, one could potentially serialize a `Metadata` struct, and then later
41
80
deserialize without any problem into a `User` struct, leading to a type-cosplay vulnerability.
42
81
43
-
**recommended**: this is secure code because all structs have an `#[account]` macro attributed
82
+
### recommended
83
+
84
+
This is secure code because all structs have an `#[account]` macro attributed
44
85
on them, thus deriving the `Discriminator` trait for each. Further, unlike the insecure-anchor
45
86
example, the program uses the proper deserialization method, `try_deserialize`, to deserialize
46
87
bytes as `User`. This is "proper" because in the derived implementation of `try_deserialize`,
47
88
the discriminator of the type is checked first.
89
+
90
+
_Note: this example differs from the Sealevel [recommended](https://github.com/coral-xyz/sealevel-attacks/blob/master/programs/3-type-cosplay/recommended/src/lib.rs) example in that it actually attempts_
91
+
_to perform a deserialization in the function body, and then uses the struct. It provides_
92
+
_a more realistic and concrete example of what might happen in real programs_
93
+
94
+
### secure
95
+
96
+
This example is from the Sealevel [example](https://github.com/coral-xyz/sealevel-attacks/blob/master/programs/3-type-cosplay/secure/src/lib.rs). It fixes the insecure case by adding a `discriminant`
97
+
field to each struct, and further this discriminant is "proper" because it contains the
98
+
necessary amount of variants in order to differentiate each type. In the code, there is
99
+
an explicit check to make sure the discriminant is as expected.
100
+
101
+
### secure-2
102
+
103
+
This example fixes both the insecure and insecure-2 examples. It is secure because it only deserializes
104
+
from a single enum, and that enum encapsulates all of the user-defined types. Since enums contain
105
+
an implicit discriminant, this program will always be secure as long as all types are defined under the enum.
0 commit comments