{-# LANGUAGE OverloadedStrings #-}
module Convex.ThreatModel.InvalidDatumIndex (
invalidDatumIndexAttack,
invalidDatumIndexAttackWith,
invalidDatumIndexAttackWithGen,
replaceConstrIndex,
) where
import Convex.ThreatModel
import Test.QuickCheck (Gen, choose)
invalidDatumIndexAttack :: ThreatModel ()
invalidDatumIndexAttack :: ThreatModel ()
invalidDatumIndexAttack = Gen Integer -> ThreatModel ()
invalidDatumIndexAttackWithGen ((Integer, Integer) -> Gen Integer
forall a. Random a => (a, a) -> Gen a
choose (Integer
3, Integer
100))
invalidDatumIndexAttackWith :: Integer -> ThreatModel ()
invalidDatumIndexAttackWith :: Integer -> ThreatModel ()
invalidDatumIndexAttackWith = Gen Integer -> ThreatModel ()
invalidDatumIndexAttackWithGen (Gen Integer -> ThreatModel ())
-> (Integer -> Gen Integer) -> Integer -> ThreatModel ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> Gen Integer
forall a. a -> Gen a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
invalidDatumIndexAttackWithGen :: Gen Integer -> ThreatModel ()
invalidDatumIndexAttackWithGen :: Gen Integer -> ThreatModel ()
invalidDatumIndexAttackWithGen Gen Integer
invalidIdxGen =
String -> ThreatModel () -> ThreatModel ()
forall a. String -> ThreatModel a -> ThreatModel a
Named String
"Invalid Datum Index Attack" (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ do
Integer
invalidIdx <- Gen Integer -> (Integer -> [Integer]) -> ThreatModel Integer
forall a. Show a => Gen a -> (a -> [a]) -> ThreatModel a
forAllTM Gen Integer
invalidIdxGen Integer -> [Integer]
forall a. a -> [a]
noShrink
Bool -> ThreatModel ()
ensure (Integer
invalidIdx Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
0)
Output
target <- (Output -> Bool) -> ThreatModel Output
anyGuardedOutputSuchThat Output -> Bool
hasConstrInlineDatum
ScriptData
originalDatum <- case Output -> Maybe ScriptData
getInlineDatum Output
target of
Maybe ScriptData
Nothing -> String -> ThreatModel ScriptData
forall a. String -> ThreatModel a
failPrecondition String
"Script output missing inline datum"
Just ScriptData
d -> ScriptData -> ThreatModel ScriptData
forall a. a -> ThreatModel a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ScriptData
d
let mutatedDatum :: ScriptData
mutatedDatum = Integer -> ScriptData -> ScriptData
replaceConstrIndex Integer
invalidIdx ScriptData
originalDatum
String -> ThreatModel ()
counterexampleTM (String -> ThreatModel ()) -> String -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[String] -> String
paragraph
[ String
"The transaction contains a script output at index"
, TxIx -> String
forall a. Show a => a -> String
show (Output -> TxIx
outputIx Output
target)
, String
"with an inline Constr datum."
]
String -> ThreatModel ()
counterexampleTM (String -> ThreatModel ()) -> String -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[String] -> String
paragraph
[ String
"Testing if the datum's constructor index can be replaced with"
, Integer -> String
forall a. Show a => a -> String
show Integer
invalidIdx
, String
"while the fields are left unchanged, and the transaction still validates."
]
String -> ThreatModel ()
counterexampleTM (String -> ThreatModel ()) -> String -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[String] -> String
paragraph
[ String
"If this validates, the script's FromData parser accepts out-of-range"
, String
"constructor indices (e.g., via a catch-all branch). An attacker could"
, String
"exploit this to:"
, String
"1) Confuse the validator about which state the datum represents"
, String
"2) Bypass state-transition guards that depend on the constructor index"
, String
"3) Lock funds permanently with an unspendable corrupted datum"
]
String -> [String] -> ThreatModel ()
tabulateTM String
"invalid index" [Integer -> String
bucketIdx Integer
invalidIdx]
TxModifier -> ThreatModel ()
shouldNotValidate (TxModifier -> ThreatModel ()) -> TxModifier -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ Output -> Datum -> TxModifier
forall t. IsInputOrOutput t => t -> Datum -> TxModifier
changeDatumOf Output
target (ScriptData -> Datum
toInlineDatum ScriptData
mutatedDatum)
noShrink :: a -> [a]
noShrink :: forall a. a -> [a]
noShrink a
_ = []
bucketIdx :: Integer -> String
bucketIdx :: Integer -> String
bucketIdx Integer
n
| Integer
n Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
10 = String
"003-010"
| Integer
n Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
50 = String
"011-050"
| Integer
n Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
100 = String
"051-100"
| Bool
otherwise = String
"101+"
replaceConstrIndex :: Integer -> ScriptData -> ScriptData
replaceConstrIndex :: Integer -> ScriptData -> ScriptData
replaceConstrIndex Integer
newIdx ScriptData
sd = case ScriptData
sd of
ScriptDataConstructor Integer
_idx [ScriptData]
fields -> Integer -> [ScriptData] -> ScriptData
ScriptDataConstructor Integer
newIdx [ScriptData]
fields
ScriptData
_ -> ScriptData
sd
hasConstrInlineDatum :: Output -> Bool
hasConstrInlineDatum :: Output -> Bool
hasConstrInlineDatum Output
output =
case Output -> Maybe ScriptData
getInlineDatum Output
output of
Just (ScriptDataConstructor{}) -> Bool
True
Maybe ScriptData
_ -> Bool
False