From: Andrii Sultanov Subject: CA-430077: Unit tests for client certificate sessions and root tasks A client certificate session has an empty auth_user_sid, the same as a root session. Check that it still can't own a task of root, neither via Server_helpers.can_own_task nor via the task-id header in Server_helpers.do_dispatch. This is CVE-2026-119779, part of XSA-530. Signed-off-by: Andrii Sultanov Reviewed-by: Ming Lu diff --git a/ocaml/tests/test_task_ownership.ml b/ocaml/tests/test_task_ownership.ml index be816dd5bc71..c81be0b50e04 100644 --- a/ocaml/tests/test_task_ownership.ml +++ b/ocaml/tests/test_task_ownership.ml @@ -24,6 +24,10 @@ let make_session ~__context ?(rbac_permissions = []) ~auth_user_sid () = Test_common.make_session ~__context ~is_local_superuser:false ~auth_user_sid ~rbac_permissions () +let make_client_cert_session ~__context = + Test_common.make_session ~__context ~is_local_superuser:false + ~auth_user_sid:"" ~client_certificate:true () + let make_task ~__context ~session_id label = let self = Ref.make () in Db.Task.create ~__context ~ref:self ~uuid:(Test_common.make_uuid ()) @@ -247,6 +251,14 @@ let test_not_own_task_of_other_user () = let session_id = make_session ~__context ~auth_user_sid:"S-1-attacker" () in check_can_own_task ~session_id ~expected:false ~task "task of another user" +let test_client_cert_does_not_own_root_task () = + let __context = Test_common.make_test_database () in + let root = Test_common.make_session ~__context ~is_local_superuser:true () in + let task = make_task ~__context ~session_id:root "task of the root" in + let session_id = make_client_cert_session ~__context in + check_can_own_task ~session_id ~expected:false ~task + "client-cert session and root task" + let test_not_own_task_without_session () = (* The Http.Unauthorised path of Xapi_http.with_context has no session at all. *) let __context = Test_common.make_test_database () in @@ -343,6 +355,17 @@ let test_dispatch_allows_without_task_header () = check_dispatch_ran ~expected:true ~session_id ~supports_async:false "no task-id header" +let test_dispatch_denies_client_cert_with_root_task () = + let __context = Test_common.make_test_database () in + let root = Test_common.make_session ~__context ~is_local_superuser:true () in + let task = make_task ~__context ~session_id:root "task of the root" in + let session_id = make_client_cert_session ~__context in + expect_rbac_denied + "task-id header from a client-cert session naming a root's task" (fun () -> + ignore (dispatch ~task ~session_id ()) + ) ; + check_task_untouched ~__context ~task "task-id header, client-cert session" + let tests = [ ( "Xapi_http.assert_credentials_ok: RBAC-denied task" @@ -394,6 +417,10 @@ let tests = , `Quick , test_own_task_of_destroy_any_holder ) + ; ( "does not own a root task from a client-cert session" + , `Quick + , test_client_cert_does_not_own_root_task + ) ] ) ; ( "Server_helpers.do_dispatch: the task-id header" @@ -414,6 +441,10 @@ let tests = , `Quick , test_dispatch_allows_without_task_header ) + ; ( "refuses a root task named by a client-cert session" + , `Quick + , test_dispatch_denies_client_cert_with_root_task + ) ] ) ]