Fault detection and integrity monitoring for kernel isolation structures
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(lint): resolve E600, E605 in csrf and delegation, apply auto-fixes

- csrf: flatten suite to single tuple, add test_csrf.mli, update runner
- delegation: split monolithic test.ml into test_name, test_trie,
test_policy, test_asn_ext, test_extract with .mli files
- delegation auto-fixes: E331 renames (make→v, make_of_path→of_path,
name→label)
- fdir auto-fixes: E105 narrow catch-all, E331 _stats→stats, add Logs

+52 -46
+15 -11
lib/fdir.ml
··· 3 3 SPDX-License-Identifier: ISC 4 4 ---------------------------------------------------------------------------*) 5 5 6 + let src = Logs.Src.create "fdir" ~doc:"Fault Detection, Isolation and Recovery" 7 + 8 + module L = (val Logs.src_log src : Logs.LOG) 9 + 6 10 type severity = Log | Isolate | Restart | Degrade | Safe_mode 7 11 type subsystem = Memory_maps | Seccomp | Cgroups 8 12 type hash = string ··· 41 45 } 42 46 43 47 let read_file path = 44 - try In_channel.with_open_text path In_channel.input_all with _ -> "" 48 + try In_channel.with_open_text path In_channel.input_all 49 + with Sys_error _ -> "" 45 50 46 51 let live () = 47 52 { ··· 141 146 } 142 147 143 148 let start_daemon ~sw ~clock ~config ~baseline ~fs ~on_anomaly () = 144 - let _stats = 149 + let stats = 145 150 ref 146 151 { 147 152 checks_total = 0; ··· 155 160 Eio.Time.sleep clock (Config.interval config); 156 161 let result = check ~baseline ~clock fs in 157 162 let now = Eio.Time.now clock in 158 - _stats := 163 + stats := 159 164 { 160 - !_stats with 161 - checks_total = !_stats.checks_total + 1; 165 + !stats with 166 + checks_total = !stats.checks_total + 1; 162 167 last_check = Some now; 163 168 }; 164 169 match result with 165 170 | Ok _ -> () 166 171 | Anomaly { anomalies; _ } -> 167 172 let severity = on_anomaly anomalies in 168 - _stats := 173 + stats := 169 174 { 170 - !_stats with 171 - anomalies_total = 172 - !_stats.anomalies_total + List.length anomalies; 175 + !stats with 176 + anomalies_total = !stats.anomalies_total + List.length anomalies; 173 177 max_severity_seen = 174 178 Some 175 - (match !_stats.max_severity_seen with 179 + (match !stats.max_severity_seen with 176 180 | None -> severity 177 181 | Some prev -> max_severity prev severity); 178 182 }; 179 - Logs.warn (fun m -> 183 + L.warn (fun m -> 180 184 m "FDIR: %d anomalies detected (severity=%a)" 181 185 (List.length anomalies) pp_severity severity) 182 186 done;
+7 -2
lib/fdir.mli
··· 42 42 43 43 val check : 44 44 baseline:snapshot -> clock:_ Eio.Time.clock -> Procfs.t -> check_result 45 - (** Compare current state against [baseline]. *) 45 + (** [check] compares the current state against the given baseline. *) 46 46 47 47 type handler = anomaly list -> severity 48 48 49 49 val default_handler : handler 50 - (** 1 subsystem changed -> [Log], 2 -> [Degrade], 3+ -> [Safe_mode]. *) 50 + (** [default_handler] returns [Log] for 1 subsystem change, [Degrade] for 2, and 51 + [Safe_mode] for 3+. *) 51 52 52 53 val pp_severity : Format.formatter -> severity -> unit 54 + 53 55 val pp_subsystem : Format.formatter -> subsystem -> unit 56 + (** Pretty-print a subsystem name. *) 54 57 55 58 module Config : sig 56 59 type t ··· 59 62 (** Default: interval=30.0s, subsystems=all three. *) 60 63 61 64 val interval : t -> float 65 + 62 66 val subsystems : t -> subsystem list 67 + (** Return the list of monitored subsystems. *) 63 68 end 64 69 65 70 type stats = {
+27 -33
test/test_fdir.ml
··· 7 7 ?(cgroups = mock_cgroups) () = 8 8 Fdir.Procfs.mock ~maps ~status ~cgroups 9 9 10 - let make_anomaly ?(expected = "x") ?(actual = "y") ?(expected_size = 1) 10 + let anomaly ?(expected = "x") ?(actual = "y") ?(expected_size = 1) 11 11 ?(actual_size = 1) sub = 12 12 { Fdir.subsystem = sub; expected; actual; expected_size; actual_size } 13 13 ··· 85 85 | Fdir.Ok _ -> () 86 86 | Fdir.Anomaly _ -> Alcotest.fail "expected Ok, got Anomaly" ) 87 87 88 - let test_check_ok_elapsed_non_negative = 88 + let test_check_ok_elapsed_nonneg = 89 89 Alcotest.test_case "check OK elapsed_ns >= 0" `Quick 90 90 ( with_eio @@ fun clock -> 91 91 let fs = mock_fs () in ··· 288 288 289 289 (* ── Anomaly record validation ──────────────────────────────────────── *) 290 290 291 - let test_anomaly_expected_hash_matches_baseline = 291 + let test_anomaly_expected_hash_baseline = 292 292 Alcotest.test_case "anomaly expected hash = baseline hash" `Quick 293 293 ( with_eio @@ fun clock -> 294 294 let baseline = Fdir.snapshot ~clock (mock_fs ()) in ··· 306 306 Alcotest.(check string) "expected hash" baseline_maps_hash a.expected 307 307 ) 308 308 309 - let test_anomaly_actual_hash_matches_current = 309 + let test_anomaly_actual_hash_current = 310 310 Alcotest.test_case "anomaly actual hash = current hash" `Quick 311 311 ( with_eio @@ fun clock -> 312 312 let baseline = Fdir.snapshot ~clock (mock_fs ()) in ··· 439 439 let test_default_handler_severity = 440 440 Alcotest.test_case "default handler 1→Log 2→Degrade 3→Safe_mode" `Quick 441 441 (fun () -> 442 - let one = [ make_anomaly Memory_maps ] in 443 - let two = [ make_anomaly Memory_maps; make_anomaly Seccomp ] in 444 - let three = 445 - [ make_anomaly Memory_maps; make_anomaly Seccomp; make_anomaly Cgroups ] 446 - in 442 + let one = [ anomaly Memory_maps ] in 443 + let two = [ anomaly Memory_maps; anomaly Seccomp ] in 444 + let three = [ anomaly Memory_maps; anomaly Seccomp; anomaly Cgroups ] in 447 445 Alcotest.(check bool) "1 -> Log" true (Fdir.default_handler one = Log); 448 446 Alcotest.(check bool) 449 447 "2 -> Degrade" true ··· 460 458 Alcotest.test_case "default handler 4+ → Safe_mode" `Quick (fun () -> 461 459 let four = 462 460 [ 463 - make_anomaly Memory_maps; 464 - make_anomaly Seccomp; 465 - make_anomaly Cgroups; 466 - make_anomaly Memory_maps; 461 + anomaly Memory_maps; 462 + anomaly Seccomp; 463 + anomaly Cgroups; 464 + anomaly Memory_maps; 467 465 ] 468 466 in 469 467 Alcotest.(check bool) 470 468 "4 -> Safe_mode" true 471 469 (Fdir.default_handler four = Safe_mode)) 472 470 473 - let test_default_handler_all_same_subsystem = 471 + let test_default_handler_same_subsys = 474 472 Alcotest.test_case "default handler: 3 anomalies same subsystem" `Quick 475 473 (fun () -> 476 474 let three = 477 - [ 478 - make_anomaly Memory_maps; 479 - make_anomaly Memory_maps; 480 - make_anomaly Memory_maps; 481 - ] 475 + [ anomaly Memory_maps; anomaly Memory_maps; anomaly Memory_maps ] 482 476 in 483 477 Alcotest.(check bool) 484 478 "3 same -> Safe_mode" true ··· 497 491 Alcotest.(check string) "same hash" a.hash b.hash) 498 492 snap1.entries snap2.entries ) 499 493 500 - let test_hash_different_data_different_hash = 494 + let test_hash_differ_data_hash = 501 495 Alcotest.test_case "different data → different hash" `Quick 502 496 ( with_eio @@ fun clock -> 503 497 let snap1 = Fdir.snapshot ~clock (mock_fs ~maps:"aaa" ()) in ··· 516 510 in 517 511 Alcotest.(check bool) "hashes differ" true (h1 <> h2) ) 518 512 519 - let test_hash_same_data_different_mock_same_hash = 513 + let test_hash_samedata_diffmock = 520 514 Alcotest.test_case "same data from different mocks → same hash" `Quick 521 515 ( with_eio @@ fun clock -> 522 516 let fs1 = mock_fs ~maps:"hello" () in ··· 690 684 Alcotest.(check string) "Seccomp" "Seccomp" (pp Seccomp); 691 685 Alcotest.(check string) "Cgroups" "Cgroups" (pp Cgroups)) 692 686 693 - let test_pp_severity_with_fmt_str = 687 + let test_pp_severity_fmtstr = 694 688 Alcotest.test_case "pp_severity works with Fmt.str" `Quick (fun () -> 695 689 let s = Fmt.str "severity=%a" Fdir.pp_severity Fdir.Safe_mode in 696 690 Alcotest.(check string) "formatted" "severity=Safe_mode" s) 697 691 698 - let test_pp_subsystem_with_fmt_str = 692 + let test_pp_subsystem_fmtstr = 699 693 Alcotest.test_case "pp_subsystem works with Fmt.str" `Quick (fun () -> 700 694 let s = Fmt.str "sub=%a" Fdir.pp_subsystem Fdir.Memory_maps in 701 695 Alcotest.(check string) "formatted" "sub=Memory_maps" s) ··· 742 736 | Fdir.Ok _ -> () 743 737 | Fdir.Anomaly _ -> Alcotest.fail "should be OK after revert" ) 744 738 745 - let test_check_with_different_corruption_each_time = 739 + let test_check_varied_corruption = 746 740 Alcotest.test_case "different corruptions produce different anomalies" `Quick 747 741 ( with_eio @@ fun clock -> 748 742 let baseline = Fdir.snapshot ~clock (mock_fs ()) in ··· 933 927 test_snapshot_subsystem_coverage; 934 928 (* Check OK *) 935 929 test_check_ok; 936 - test_check_ok_elapsed_non_negative; 930 + test_check_ok_elapsed_nonneg; 937 931 test_check_ok_repeated; 938 932 (* Single subsystem corruption *) 939 933 test_detect_maps_change; ··· 953 947 test_binary_data_corruption; 954 948 test_large_data_corruption; 955 949 (* Anomaly record validation *) 956 - test_anomaly_expected_hash_matches_baseline; 957 - test_anomaly_actual_hash_matches_current; 950 + test_anomaly_expected_hash_baseline; 951 + test_anomaly_actual_hash_current; 958 952 test_anomaly_expected_size; 959 953 test_anomaly_actual_size; 960 954 test_anomaly_elapsed_non_negative; ··· 968 962 test_default_handler_severity; 969 963 test_default_handler_empty; 970 964 test_default_handler_four_anomalies; 971 - test_default_handler_all_same_subsystem; 965 + test_default_handler_same_subsys; 972 966 (* Hash properties *) 973 967 test_hash_determinism; 974 - test_hash_different_data_different_hash; 975 - test_hash_same_data_different_mock_same_hash; 968 + test_hash_differ_data_hash; 969 + test_hash_samedata_diffmock; 976 970 test_hash_avalanche; 977 971 test_empty_input_valid_hash; 978 972 test_empty_strings_same_hash; ··· 989 983 test_pp_functions; 990 984 test_pp_severity_values; 991 985 test_pp_subsystem_values; 992 - test_pp_severity_with_fmt_str; 993 - test_pp_subsystem_with_fmt_str; 986 + test_pp_severity_fmtstr; 987 + test_pp_subsystem_fmtstr; 994 988 (* Cross-mock consistency *) 995 989 test_independent_subsystem_hashes; 996 990 test_check_after_revert_ok; 997 - test_check_with_different_corruption_each_time; 991 + test_check_varied_corruption; 998 992 (* Realistic procfs *) 999 993 test_realistic_snapshot; 1000 994 test_realistic_check_ok;
+3
test/test_fdir.mli
··· 1 + (** FDIR test suite. *) 2 + 1 3 val suite : string * unit Alcotest.test_case list 4 + (** The FDIR test suite. *)