@@ -11,6 +11,7 @@ module DynamoToStruct {
11
11
import opened Wrappers
12
12
import opened StandardLibrary
13
13
import opened StandardLibrary. UInt
14
+ import opened DynamoDbEncryptionUtil
14
15
import AwsCryptographyDbEncryptionSdkDynamoDbTypes
15
16
import UTF8
16
17
import SortedSets
@@ -264,19 +265,20 @@ module DynamoToStruct {
264
265
265
266
// convert AttributeValue to byte sequence
266
267
// if `prefix` is true, prefix sequence with TypeID and Length
267
- function method {:opaque} AttrToBytes (a : AttributeValue , prefix : bool ) : (ret : Result< seq < uint8> , string > )
268
+ function method {:opaque} AttrToBytes (a : AttributeValue , prefix : bool , depth : nat := 1 ) : (ret : Result< seq < uint8> , string > )
268
269
decreases a
269
270
ensures ret. Success? && prefix ==> 6 <= |ret. value|
271
+ ensures MAX_STRUCTURE_DEPTH < depth ==> ret. Failure?
270
272
271
273
// = specification/dynamodb-encryption-client/ddb-attribute-serialization.md#boolean
272
274
// = type=implication
273
275
// # Boolean MUST be serialized as:
274
276
// # - `0x00` if the value is `false`
275
277
// # - `0x01` if the value is `true`
276
- ensures a. BOOL? && ! prefix ==>
278
+ ensures a. BOOL? && ! prefix && depth <= MAX_STRUCTURE_DEPTH ==>
277
279
&& (a. BOOL ==> ret. Success? && |ret. value| == BOOL_LEN && ret. value[0] == 1)
278
280
&& (! a. BOOL ==> ret. Success? && |ret. value| == BOOL_LEN && ret. value[0] == 0)
279
- ensures a. BOOL? && prefix ==>
281
+ ensures a. BOOL? && prefix && depth <= MAX_STRUCTURE_DEPTH ==>
280
282
&& (a. BOOL ==> (ret. Success? && |ret. value| == PREFIX_LEN+ BOOL_LEN && ret. value[PREFIX_LEN] == 1
281
283
&& ret. value[0.. TYPEID_LEN] == BOOLEAN && ret. value[TYPEID_LEN.. PREFIX_LEN] == [0,0,0,1]))
282
284
&& (! a. BOOL ==> (ret. Success? && |ret. value| == PREFIX_LEN+ BOOL_LEN && ret. value[PREFIX_LEN] == 0
@@ -286,8 +288,8 @@ module DynamoToStruct {
286
288
// = type=implication
287
289
// # Binary MUST be serialized with the identity function;
288
290
// # or more plainly, Binary Attribute Values are used as is.
289
- ensures a. B? && ! prefix ==> ret. Success? && ret. value == a. B
290
- ensures a. B? && prefix && ret. Success? ==>
291
+ ensures a. B? && ! prefix && depth <= MAX_STRUCTURE_DEPTH ==> ret. Success? && ret. value == a. B
292
+ ensures a. B? && prefix && ret. Success? && depth <= MAX_STRUCTURE_DEPTH ==>
291
293
&& ret. value[PREFIX_LEN.. ] == a. B
292
294
&& ret. value[0.. TYPEID_LEN] == BINARY
293
295
&& U32ToBigEndian (|a.B|). Success?
@@ -297,8 +299,8 @@ module DynamoToStruct {
297
299
// = specification/dynamodb-encryption-client/ddb-attribute-serialization.md#null
298
300
// = type=implication
299
301
// # Null MUST be serialized as a zero-length byte string.
300
- ensures a. NULL? && ! prefix ==> ret. Success? && |ret. value| == 0
301
- ensures a. NULL? && prefix ==> ret. Success? && |ret. value| == PREFIX_LEN && ret. value[0.. TYPEID_LEN] == NULL && ret. value[TYPEID_LEN.. PREFIX_LEN] == [0,0,0,0]
302
+ ensures a. NULL? && ! prefix && depth <= MAX_STRUCTURE_DEPTH ==> ret. Success? && |ret. value| == 0
303
+ ensures a. NULL? && prefix && depth <= MAX_STRUCTURE_DEPTH ==> ret. Success? && |ret. value| == PREFIX_LEN && ret. value[0.. TYPEID_LEN] == NULL && ret. value[TYPEID_LEN.. PREFIX_LEN] == [0,0,0,0]
302
304
303
305
// = specification/dynamodb-encryption-client/ddb-attribute-serialization.md#string
304
306
// = type=implication
@@ -463,15 +465,16 @@ module DynamoToStruct {
463
465
&& (|a. M| == 0 ==> |ret. value| == PREFIX_LEN + LENGTH_LEN)
464
466
465
467
{
468
+ :- Need (depth <= MAX_STRUCTURE_DEPTH, "Depth of attribute structure to serialize exceeds limit of " + MAX_STRUCTURE_DEPTH_STR);
466
469
var baseBytes :- match a {
467
470
case S (s) => UTF8. Encode (s)
468
471
case N (n) => var nn :- Norm. NormalizeNumber (n); UTF8. Encode (nn)
469
472
case B (b) => Success (b)
470
473
case SS (ss) => StringSetAttrToBytes (ss)
471
474
case NS (ns) => NumberSetAttrToBytes (ns)
472
475
case BS (bs) => BinarySetAttrToBytes (bs)
473
- case M (m) => MapAttrToBytes (a, m)
474
- case L (l) => ListAttrToBytes (l)
476
+ case M (m) => MapAttrToBytes (a, m, depth )
477
+ case L (l) => ListAttrToBytes (l, depth )
475
478
case NULL (n) => Success ([])
476
479
case BOOL (b) => Success ([BoolToUint8(b)])
477
480
};
@@ -516,7 +519,7 @@ module DynamoToStruct {
516
519
// along with the corresponding precondition,
517
520
// lets Dafny find the correct termination metric.
518
521
// See "The Parent Trick" for details: <https://leino.science/papers/krml283.html>.
519
- function method MapAttrToBytes (ghost parent: AttributeValue , m: MapAttributeValue ): (ret: Result< seq < uint8> , string > )
522
+ function method MapAttrToBytes (ghost parent: AttributeValue , m: MapAttributeValue , depth : nat ): (ret: Result< seq < uint8> , string > )
520
523
requires forall kv < - m. Items :: kv. 1 < parent
521
524
{
522
525
// = specification/dynamodb-encryption-client/ddb-attribute-serialization.md#value-type
@@ -532,17 +535,17 @@ module DynamoToStruct {
532
535
// = specification/dynamodb-encryption-client/ddb-attribute-serialization.md#map-value
533
536
// # A Map MAY hold any DynamoDB Attribute Value data type,
534
537
// # and MAY hold values of different types.
535
- var bytesResults := map kv < - m. Items :: kv. 0 := AttrToBytes (kv.1, true);
538
+ var bytesResults := map kv < - m. Items :: kv. 0 := AttrToBytes (kv.1, true, depth+1 );
536
539
var count :- U32ToBigEndian (|m|);
537
540
var bytes :- SimplifyMapValue (bytesResults);
538
541
var body :- CollectMap (bytes);
539
542
Success (count + body)
540
543
}
541
544
542
- function method ListAttrToBytes (l: ListAttributeValue ): (ret: Result< seq < uint8> , string > )
545
+ function method ListAttrToBytes (l: ListAttributeValue , depth : nat ): (ret: Result< seq < uint8> , string > )
543
546
{
544
547
var count :- U32ToBigEndian (|l|);
545
- var body :- CollectList (l);
548
+ var body :- CollectList (l, depth );
546
549
Success (count + body)
547
550
}
548
551
@@ -672,6 +675,7 @@ module DynamoToStruct {
672
675
// # and MAY hold values of different types.
673
676
function method {:opaque} CollectList (
674
677
listToSerialize : ListAttributeValue ,
678
+ depth : nat ,
675
679
serialized : seq <uint8 > := []
676
680
)
677
681
: (ret : Result< seq < uint8> , string > )
@@ -681,8 +685,8 @@ module DynamoToStruct {
681
685
if |listToSerialize| == 0 then
682
686
Success (serialized)
683
687
else
684
- var val :- AttrToBytes (listToSerialize[0], true);
685
- CollectList (listToSerialize[1..], serialized + val)
688
+ var val :- AttrToBytes (listToSerialize[0], true, depth+1 );
689
+ CollectList (listToSerialize[1..], depth, serialized + val)
686
690
}
687
691
688
692
function method SerializeMapItem (key : string , value : seq <uint8 >) : (ret : Result< seq < uint8> , string > )
@@ -881,7 +885,8 @@ module DynamoToStruct {
881
885
function method {:vcs_split_on_every_assert} {:opaque} DeserializeList (
882
886
serialized : seq <uint8 >,
883
887
remainingCount : nat ,
884
- origSerializedSize : nat ,
888
+ ghost origSerializedSize : nat ,
889
+ depth : nat ,
885
890
resultList : AttrValueAndLength )
886
891
: (ret : Result< AttrValueAndLength, string > )
887
892
requires resultList. val. L?
@@ -902,17 +907,18 @@ module DynamoToStruct {
902
907
if |serialized| < len then
903
908
Failure ("Out of bytes reading Content of List element")
904
909
else
905
- var nval :- BytesToAttr (serialized[..len], TerminalTypeId, false);
910
+ var nval :- BytesToAttr (serialized[..len], TerminalTypeId, false, depth+1 );
906
911
var nattr := AttributeValue. L (resultList.val.L + [nval.val]);
907
- DeserializeList (serialized[len..], remainingCount-1, origSerializedSize, AttrValueAndLength(nattr, resultList.len + len + 6))
912
+ DeserializeList (serialized[len..], remainingCount-1, origSerializedSize, depth, AttrValueAndLength(nattr, resultList.len + len + 6))
908
913
}
909
914
910
915
// Bytes to Map
911
916
// Can't be {:tailrecursion} because it calls BytesToAttr which might again call DeserializeMap
912
917
function method {:vcs_split_on_every_assert} {:opaque} DeserializeMap (
913
918
serialized : seq <uint8 >,
914
919
remainingCount : nat ,
915
- origSerializedSize : nat ,
920
+ ghost origSerializedSize : nat ,
921
+ depth : nat ,
916
922
resultMap : AttrValueAndLength )
917
923
: (ret : Result< AttrValueAndLength, string > )
918
924
requires resultMap. val. M?
@@ -948,7 +954,7 @@ module DynamoToStruct {
948
954
var serialized := serialized[2.. ];
949
955
950
956
// get value and construct result
951
- var nval :- BytesToAttr (serialized, TerminalTypeId_value, true);
957
+ var nval :- BytesToAttr (serialized, TerminalTypeId_value, true, depth+1 );
952
958
var serialized := serialized[nval. len.. ];
953
959
954
960
// = specification/dynamodb-encryption-client/ddb-attribute-serialization.md#key-value-pair-entries
@@ -960,15 +966,23 @@ module DynamoToStruct {
960
966
var nattr := AttributeValue. M (resultMap.val.M[key := nval.val]);
961
967
var newResultMap := AttrValueAndLength (nattr, resultMap.len + nval.len + 8 + len);
962
968
assert |serialized| + newResultMap. len == origSerializedSize;
963
- DeserializeMap (serialized, remainingCount - 1, origSerializedSize, newResultMap)
969
+ DeserializeMap (serialized, remainingCount - 1, origSerializedSize, depth, newResultMap)
964
970
}
965
971
966
972
// Bytes to AttributeValue
967
973
// Can't be {:tailrecursion} because it calls DeserializeList and DeserializeMap which then call BytesToAttr
968
- function method {:vcs_split_on_every_assert} {:opaque} BytesToAttr (value : seq <uint8 >, typeId : TerminalTypeId , hasLen : bool ) : (ret : Result< AttrValueAndLength, string > )
974
+ function method {:vcs_split_on_every_assert} {:opaque} BytesToAttr (
975
+ value : seq <uint8 >,
976
+ typeId : TerminalTypeId ,
977
+ hasLen : bool ,
978
+ depth : nat := 1
979
+ )
980
+ : (ret : Result< AttrValueAndLength, string > )
969
981
ensures ret. Success? ==> ret. value. len <= |value|
982
+ ensures MAX_STRUCTURE_DEPTH < depth ==> ret. Failure?
970
983
decreases |value|
971
984
{
985
+ :- Need (depth <= MAX_STRUCTURE_DEPTH, "Depth of attribute structure to deserialize exceeds limit of " + MAX_STRUCTURE_DEPTH_STR);
972
986
var len :- if hasLen then
973
987
if |value| < LENGTH_LEN then
974
988
Failure ("Out of bytes reading length")
@@ -1043,15 +1057,15 @@ module DynamoToStruct {
1043
1057
else
1044
1058
var len :- BigEndianToU32 (value);
1045
1059
var value := value[LENGTH_LEN.. ];
1046
- DeserializeMap (value, len, |value| + LENGTH_LEN + lengthBytes, AttrValueAndLength(AttributeValue.M(map[]), LENGTH_LEN + lengthBytes))
1060
+ DeserializeMap (value, len, |value| + LENGTH_LEN + lengthBytes, depth, AttrValueAndLength(AttributeValue.M(map[]), LENGTH_LEN + lengthBytes))
1047
1061
1048
1062
else if typeId == LIST then
1049
1063
if |value| < LENGTH_LEN then
1050
1064
Failure ("List Structured Data has less than 4 bytes")
1051
1065
else
1052
1066
var len :- BigEndianToU32 (value);
1053
1067
var value := value[LENGTH_LEN.. ];
1054
- DeserializeList (value, len, |value| + LENGTH_LEN + lengthBytes, AttrValueAndLength(AttributeValue.L([]), LENGTH_LEN + lengthBytes))
1068
+ DeserializeList (value, len, |value| + LENGTH_LEN + lengthBytes, depth, AttrValueAndLength(AttributeValue.L([]), LENGTH_LEN + lengthBytes))
1055
1069
1056
1070
else
1057
1071
Failure ("Unsupported TerminalTypeId")
0 commit comments