{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
module Convex.ThreatModel.SelfReferenceInjection (
selfReferenceInjection,
selfReferenceInjectionWith,
injectScriptCredential,
isAddressLikeStructure,
) where
import Convex.ThreatModel
import Data.ByteString qualified as BS
selfReferenceInjection :: ThreatModel ()
selfReferenceInjection :: ThreatModel ()
selfReferenceInjection = Bool -> ThreatModel ()
selfReferenceInjectionWith Bool
False
selfReferenceInjectionWith :: Bool -> ThreatModel ()
selfReferenceInjectionWith :: Bool -> ThreatModel ()
selfReferenceInjectionWith Bool
verbose = String -> ThreatModel () -> ThreatModel ()
forall a. String -> ThreatModel a -> ThreatModel a
Named String
"Self-Reference Injection" (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ do
[Input]
inputs <- ThreatModel [Input]
getTxInputs
let scriptInputs :: [Input]
scriptInputs = (Input -> Bool) -> [Input] -> [Input]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Input -> Bool) -> Input -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AddressAny -> Bool
isKeyAddressAny (AddressAny -> Bool) -> (Input -> AddressAny) -> Input -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Input -> AddressAny
forall t. IsInputOrOutput t => t -> AddressAny
addressOf) [Input]
inputs
ThreatModel () -> ThreatModel ()
forall a. ThreatModel a -> ThreatModel a
threatPrecondition (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ Bool -> ThreatModel ()
ensure (Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ [Input] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Input]
scriptInputs)
Input
scriptInput <- [Input] -> ThreatModel Input
forall a. Show a => [a] -> ThreatModel a
pickAny [Input]
scriptInputs
let scriptAddr :: AddressAny
scriptAddr = Input -> AddressAny
forall t. IsInputOrOutput t => t -> AddressAny
addressOf Input
scriptInput
ByteString
credBytes <- case AddressAny -> Maybe ByteString
extractScriptCredential AddressAny
scriptAddr of
Maybe ByteString
Nothing -> String -> ThreatModel ByteString
forall a. String -> ThreatModel a
failPrecondition String
"Script output missing"
Just ByteString
credBytes' -> ByteString -> ThreatModel ByteString
forall a. a -> ThreatModel a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ByteString
credBytes'
(Output
target, ScriptData
originalDatum) <- ThreatModel (Output, ScriptData)
anyGuardedOutputWithInlineDatum
let scriptCredData :: ScriptData
scriptCredData = Integer -> [ScriptData] -> ScriptData
ScriptDataConstructor Integer
1 [ByteString -> ScriptData
ScriptDataBytes ByteString
credBytes]
let modifiedDatum :: ScriptData
modifiedDatum = ScriptData -> ScriptData -> ScriptData
injectScriptCredential ScriptData
scriptCredData ScriptData
originalDatum
ThreatModel () -> ThreatModel ()
forall a. ThreatModel a -> ThreatModel a
threatPrecondition (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ Bool -> ThreatModel ()
ensure (ScriptData
modifiedDatum ScriptData -> ScriptData -> Bool
forall a. Eq a => a -> a -> Bool
/= ScriptData
originalDatum)
Bool -> ThreatModel () -> ThreatModel ()
forall {f :: * -> *}. Applicative f => Bool -> f () -> f ()
when Bool
verbose (ThreatModel () -> ThreatModel ())
-> ThreatModel () -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$ do
String -> ThreatModel ()
counterexampleTM (String -> ThreatModel ()) -> String -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[String] -> String
paragraph
[ String
"The transaction contains a script input at address"
, Doc -> String
forall a. Show a => a -> String
show (AddressAny -> Doc
prettyAddress AddressAny
scriptAddr)
, String
"and a continuation output at index"
, TxIx -> String
forall a. Show a => a -> String
show (Output -> TxIx
outputIx Output
target)
, String
"with an inline datum."
]
String -> ThreatModel ()
counterexampleTM (String -> ThreatModel ()) -> String -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[String] -> String
paragraph
[ String
"Testing if address fields in the datum can be replaced with"
, String
"the script's own address while still passing validation."
]
String -> ThreatModel ()
counterexampleTM (String -> ThreatModel ()) -> String -> ThreatModel ()
forall a b. (a -> b) -> a -> b
$
[String] -> String
paragraph
[ String
"Self-reference injection: replaced address credentials in datum"
, String
"with the script's own credential. If this validates, the script"
, String
"doesn't prevent setting address fields to its own address,"
, String
"allowing bypass of payment requirements."
]
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
modifiedDatum)
where
when :: Bool -> f () -> f ()
when Bool
False f ()
_ = () -> f ()
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
when Bool
True f ()
action = f ()
action
extractScriptCredential :: AddressAny -> Maybe BS.ByteString
AddressAny
addr =
case AddressAny
addr of
AddressShelley (ShelleyAddress Network
_ PaymentCredential
cred StakeReference
_) ->
case PaymentCredential -> PaymentCredential
fromShelleyPaymentCredential PaymentCredential
cred of
PaymentCredentialByScript ScriptHash
sh ->
ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (ByteString -> Maybe ByteString) -> ByteString -> Maybe ByteString
forall a b. (a -> b) -> a -> b
$ ScriptHash -> ByteString
forall a. SerialiseAsRawBytes a => a -> ByteString
serialiseToRawBytes ScriptHash
sh
PaymentCredentialByKey Hash PaymentKey
_ ->
Maybe ByteString
forall a. Maybe a
Nothing
AddressByron Address ByronAddr
_ ->
Maybe ByteString
forall a. Maybe a
Nothing
injectScriptCredential :: ScriptData -> ScriptData -> ScriptData
injectScriptCredential :: ScriptData -> ScriptData -> ScriptData
injectScriptCredential ScriptData
newCred = ScriptData -> ScriptData
go
where
go :: ScriptData -> ScriptData
go (ScriptDataConstructor Integer
0 [ScriptData
cred, ScriptData
stakingCred])
| ScriptData -> Bool
isCredentialLike ScriptData
cred =
Integer -> [ScriptData] -> ScriptData
ScriptDataConstructor Integer
0 [ScriptData
newCred, ScriptData
stakingCred]
go (ScriptDataConstructor Integer
idx [ScriptData]
fields) =
Integer -> [ScriptData] -> ScriptData
ScriptDataConstructor Integer
idx ((ScriptData -> ScriptData) -> [ScriptData] -> [ScriptData]
forall a b. (a -> b) -> [a] -> [b]
map ScriptData -> ScriptData
go [ScriptData]
fields)
go (ScriptDataList [ScriptData]
items) =
[ScriptData] -> ScriptData
ScriptDataList ((ScriptData -> ScriptData) -> [ScriptData] -> [ScriptData]
forall a b. (a -> b) -> [a] -> [b]
map ScriptData -> ScriptData
go [ScriptData]
items)
go (ScriptDataMap [(ScriptData, ScriptData)]
entries) =
[(ScriptData, ScriptData)] -> ScriptData
ScriptDataMap [(ScriptData -> ScriptData
go ScriptData
k, ScriptData -> ScriptData
go ScriptData
v) | (ScriptData
k, ScriptData
v) <- [(ScriptData, ScriptData)]
entries]
go ScriptData
other = ScriptData
other
isAddressLikeStructure :: ScriptData -> Bool
isAddressLikeStructure :: ScriptData -> Bool
isAddressLikeStructure (ScriptDataConstructor Integer
0 [ScriptData
cred, ScriptData
_stakingCred]) =
ScriptData -> Bool
isCredentialLike ScriptData
cred
isAddressLikeStructure ScriptData
_ = Bool
False
isCredentialLike :: ScriptData -> Bool
isCredentialLike :: ScriptData -> Bool
isCredentialLike (ScriptDataConstructor Integer
n [ScriptDataBytes ByteString
bs]) =
Integer
n Integer -> [Integer] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Integer
0, Integer
1] Bool -> Bool -> Bool
&& ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
28
isCredentialLike ScriptData
_ = Bool
False