Skip to content

Commit 4a5f925

Browse files
authored
Add security-related unit tests (#29)
Adds a unit test making sure XSA-483 is fixed, and a few more verifying other security guarantees are ensured (history is bounded; concurrent transactions can't circumvent quota checks). Also adds tests for the `Unwatch` command.
2 parents 92f43a4 + 173095a commit 4a5f925

1 file changed

Lines changed: 160 additions & 2 deletions

File tree

tests/unit_tests.ml

Lines changed: 160 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,40 @@ let test_mkdir () =
168168
; (dom0, tid, (Transaction_end, ["T"]), (Transaction_end, ["OK"]))
169169
]
170170

171+
let check_history_length expected =
172+
Alcotest.(check' int)
173+
~msg:"Check history length is as expected"
174+
~actual:(List.length !History.history)
175+
~expected
176+
177+
let test_history_trim () =
178+
initialize_main_loop () ;
179+
let one_loop_iteration, store, cons, doms = Xenstored.main () in
180+
let dom0 = Hashtbl.find cons.domains 0 in
181+
let dom1 = create_domU_conn cons doms 1 in
182+
183+
run store cons doms
184+
[
185+
(dom0, none, (Write, ["/local/domain/1"; ""]), (Write, ["OK"]))
186+
; (dom0, none, (Setperms, ["/local/domain/1"; "n1"]), (Setperms, ["OK"]))
187+
] ;
188+
189+
(* Start a long-running transaction *)
190+
one_loop_iteration () ;
191+
let _tid = start_transaction store cons doms dom1 in
192+
Unix.sleepf !Define.conflict_max_history_seconds ;
193+
194+
for _ = 1 to 100 do
195+
run store cons doms
196+
[(dom1, none, (Write, ["/local/domain/1/a"; ""]), (Write, ["OK"]))]
197+
done ;
198+
(* Without running frequent_ops, history list is long *)
199+
check_history_length 100 ;
200+
201+
(* frequent_ops trims history list, removing long-running transactions *)
202+
one_loop_iteration () ;
203+
check_history_length 0
204+
171205
(* Check that I can read an empty value *)
172206
let test_empty () =
173207
let store, doms, cons = initialize () in
@@ -367,7 +401,20 @@ let test_simple_watches () =
367401
; (dom0, none, (Read, ["/a/1"]), (Error, ["ENOENT"]))
368402
; (dom1, none, (Read, ["/a"]), (Read, ["foo\000"]))
369403
; (dom1, none, (Read, ["/a/1"]), (Error, ["ENOENT"]))
370-
]
404+
] ;
405+
406+
(* Unwatch returns an error on a nonexistent path/token,
407+
removes the watch otherwise.
408+
Different connection can't touch other's watches. *)
409+
run store cons doms
410+
[
411+
(dom0, none, (Unwatch, ["/b"; "token"]), (Error, ["ENOENT"]))
412+
; (dom0, none, (Unwatch, ["/a"; "wrongtoken"]), (Error, ["ENOENT"]))
413+
; (dom1, none, (Unwatch, ["/a"; "token"]), (Error, ["ENOENT"]))
414+
; (dom0, none, (Unwatch, ["/a"; "token"]), (Unwatch, ["OK"]))
415+
; (dom0, none, (Unwatch, ["/a"; "token"]), (Error, ["ENOENT"]))
416+
] ;
417+
assert_watches dom0 []
371418

372419
(* Check watches on relative paths *)
373420
let test_relative_watches () =
@@ -394,7 +441,18 @@ let test_relative_watches () =
394441
)
395442
] ;
396443
check_for_watchevent dom0 "device/vbd" "token" ;
397-
assert_watches dom0 [("device", "token", None)]
444+
assert_watches dom0 [("device", "token", None)] ;
445+
446+
(* Unwatch returns an error on a nonexistent path/token,
447+
removes the watch otherwise *)
448+
run store cons doms
449+
[
450+
(dom0, none, (Unwatch, ["devices"; "token"]), (Error, ["ENOENT"]))
451+
; (dom0, none, (Unwatch, ["device"; "wrongtoken"]), (Error, ["ENOENT"]))
452+
; (dom0, none, (Unwatch, ["device"; "token"]), (Unwatch, ["OK"]))
453+
; (dom0, none, (Unwatch, ["device"; "token"]), (Error, ["ENOENT"]))
454+
] ;
455+
assert_watches dom0 []
398456

399457
(* Check that a connection only receives a watch if it
400458
can read the node that was modified. *)
@@ -782,6 +840,41 @@ let check_quota_ent_per_domain store ~domid expected =
782840
~actual:(get_current_entries_quota store domid)
783841
~expected
784842

843+
let test_xsa_483 () =
844+
initialize_main_loop () ;
845+
let one_loop_iteration, store, cons, doms = Xenstored.main () in
846+
let dom0 = Hashtbl.find cons.domains 0 in
847+
848+
(* Domains > 2000 are considered dead on the first query for test purposes *)
849+
let domU = create_domU_conn cons doms 2001 in
850+
851+
run store cons doms
852+
[
853+
(dom0, none, (Write, ["/local/domain/2001"; ""]), (Write, ["OK"]))
854+
; ( dom0
855+
, none
856+
, (Setperms, ["/local/domain/2001"; "r2001"])
857+
, (Setperms, ["OK"])
858+
)
859+
] ;
860+
861+
check_quota_ent_per_domain store ~domid:2001 1 ;
862+
863+
(* domU adds some nodes to its sub-tree *)
864+
run store cons doms
865+
[
866+
(domU, none, (Write, ["/local/domain/2001/x"; ""]), (Write, ["OK"]))
867+
; (domU, none, (Write, ["/local/domain/2001/y"; ""]), (Write, ["OK"]))
868+
; (domU, none, (Write, ["/local/domain/2001/z"; ""]), (Write, ["OK"]))
869+
] ;
870+
check_quota_ent_per_domain store ~domid:2001 4 ;
871+
872+
(* dom2001 dies, is cleaned up *)
873+
one_loop_iteration () ;
874+
875+
(* Its quota should be reset back to 0 *)
876+
check_quota_ent_per_domain store ~domid:2001 0
877+
785878
(* Check that node creation and destruction changes a quota *)
786879
let test_quota () =
787880
let store, doms, cons = initialize () in
@@ -871,6 +964,65 @@ let test_quota_transaction () =
871964
check_quota_ent_per_domain store ~domid:1 2 ;
872965
check_quota_ent_per_domain store ~domid:2 4
873966

967+
let test_quota_transaction_overflow () =
968+
let store, doms, cons = initialize () in
969+
let dom0 = create_dom0_conn cons doms in
970+
let dom1 = create_domU_conn cons doms 1 in
971+
972+
store.quota <- {store.quota with maxent= 3} ;
973+
run store cons doms
974+
[
975+
(dom0, none, (Write, ["/local/domain/1/attr/x"; ""]), (Write, ["OK"]))
976+
; (dom0, none, (Write, ["/local/domain/1/attr/y"; ""]), (Write, ["OK"]))
977+
; ( dom0
978+
, none
979+
, (Setperms, ["/local/domain/1/attr/x"; "r1"])
980+
, (Setperms, ["OK"])
981+
)
982+
; ( dom0
983+
, none
984+
, (Setperms, ["/local/domain/1/attr/y"; "r1"])
985+
, (Setperms, ["OK"])
986+
)
987+
] ;
988+
check_quota_ent_per_domain store ~domid:1 2 ;
989+
990+
(* dom1 should only be able to create one more node now *)
991+
992+
(* Creating two nodes in one transaction fails with EQUOTA during the transaction *)
993+
let tid_0 = start_transaction store cons doms dom1 in
994+
run store cons doms
995+
[
996+
(dom1, tid_0, (Write, ["/local/domain/1/attr/x/1"; ""]), (Write, ["OK"]))
997+
; ( dom1
998+
, tid_0
999+
, (Write, ["/local/domain/1/attr/y/1"; ""])
1000+
, (Error, ["EQUOTA"])
1001+
)
1002+
] ;
1003+
1004+
(* Two transactions create a node each - writes need to be coalescable *)
1005+
let tid_1 = start_transaction store cons doms dom1 in
1006+
let tid_2 = start_transaction store cons doms dom1 in
1007+
run store cons doms
1008+
[
1009+
(dom1, tid_1, (Write, ["/local/domain/1/attr/x/1"; ""]), (Write, ["OK"]))
1010+
; (dom1, tid_2, (Write, ["/local/domain/1/attr/y/1"; ""]), (Write, ["OK"]))
1011+
] ;
1012+
1013+
(* Both transactions return OK, but EQUOTA is generated during transaction
1014+
replay and nodes are not created over the limit *)
1015+
run store cons doms
1016+
[
1017+
(dom1, tid_1, (Transaction_end, ["T"]), (Transaction_end, ["OK"]))
1018+
; (dom1, tid_2, (Transaction_end, ["T"]), (Transaction_end, ["OK"]))
1019+
] ;
1020+
run store cons doms
1021+
[
1022+
(dom1, tid_0, (Read, ["/local/domain/1/attr/x/1"]), (Read, ["\000"]))
1023+
; (dom1, tid_0, (Read, ["/local/domain/1/attr/y/1"]), (Error, ["ENOENT"]))
1024+
]
1025+
8741026
(* Check that string length quota is checked correctly *)
8751027
let test_quota_maxsize () =
8761028
let store, doms, cons = initialize () in
@@ -983,6 +1135,7 @@ let () =
9831135
, `Quick
9841136
, test_transactions_really_do_conflict
9851137
)
1138+
; ("test_history_trim", `Quick, test_history_trim)
9861139
]
9871140
)
9881141
; ( "Watches tests"
@@ -1008,7 +1161,12 @@ let () =
10081161
; ( "Quota tests"
10091162
, [
10101163
("test_quota", `Quick, test_quota)
1164+
; ("test_xsa_483", `Quick, test_xsa_483)
10111165
; ("test_quota_transaction", `Quick, test_quota_transaction)
1166+
; ( "test_quota_transaction_overflow"
1167+
, `Quick
1168+
, test_quota_transaction_overflow
1169+
)
10121170
; ("test_quota_maxsize", `Quick, test_quota_maxsize)
10131171
; ("test_quota_maxent", `Quick, test_quota_maxent)
10141172
]

0 commit comments

Comments
 (0)