The branch main has been updated by asomers:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=ece617cda6b5999e6e57c2227f98c18c1ca1d79d

commit ece617cda6b5999e6e57c2227f98c18c1ca1d79d
Author:     Alan Somers <asom...@freebsd.org>
AuthorDate: 2024-10-03 21:52:44 +0000
Commit:     Alan Somers <asom...@freebsd.org>
CommitDate: 2025-06-15 15:55:15 +0000

    ctl: add ATF tests for SCSI PERSISTENT RESERVE IN/OUT
    
    These commands are complicated, and the test suite isn't complete.  But
    it's a start.  And it includes a regression test for HYP-08
    (commit 64b0f52be2c9d7bcecebfeef393f8ec56cb85f47).
    
    MFC after:      2 weeks
    Security:       HYP-08
    Sponsored by:   ConnectWise
    Differential Revision: https://reviews.freebsd.org/D46947
---
 tests/sys/cam/ctl/Makefile                  |   6 +
 tests/sys/cam/ctl/persist.sh                | 349 ++++++++++++++++++++++++++++
 tests/sys/cam/ctl/prout_register_huge_cdb.c |  88 +++++++
 3 files changed, 443 insertions(+)

diff --git a/tests/sys/cam/ctl/Makefile b/tests/sys/cam/ctl/Makefile
index 1333397af464..05f0831fc8b0 100644
--- a/tests/sys/cam/ctl/Makefile
+++ b/tests/sys/cam/ctl/Makefile
@@ -1,13 +1,19 @@
 PACKAGE=       tests
 
 TESTSDIR=      ${TESTSBASE}/sys/cam/ctl
+BINDIR=${TESTSDIR}
 
 ${PACKAGE}FILES+=      ctl.subr
 
+ATF_TESTS_SH+= persist
 ATF_TESTS_SH+= prevent
 ATF_TESTS_SH+= read_buffer
 ATF_TESTS_SH+= start_stop_unit
 
+PROGS+=        prout_register_huge_cdb
+LIBADD+=       cam
+CFLAGS+=       -I${SRCTOP}/sys
+
 # Must be exclusive because it disables/enables camsim
 TEST_METADATA+=        is_exclusive="true"
 
diff --git a/tests/sys/cam/ctl/persist.sh b/tests/sys/cam/ctl/persist.sh
new file mode 100644
index 000000000000..2a350ee4775a
--- /dev/null
+++ b/tests/sys/cam/ctl/persist.sh
@@ -0,0 +1,349 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2024 ConnectWise
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS DOCUMENTATION IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+. $(atf_get_srcdir)/ctl.subr
+
+# TODO
+# * PRIN READ RESERVATION, with one reservation
+# * PROUT with illegal type
+# * PROUT REGISTER AND IGNORE EXISTING KEY
+# * PROUT REGISTER AND IGNORE EXISTING KEY with a RESERVATION KEY that isn't 
registered
+# * PROUT REGISTER AND IGNORE EXISTING KEY to unregister
+# * PROUT CLEAR allows previously prevented medium removal
+# * PROUT PREEMPT
+# * PROUT PREEMPT with a RESERVATION KEY that isn't registered
+# * PROUT PREEMPT_AND_ABORT
+# * PROUT PREEMPT_AND_ABORT with a RESERVATION KEY that isn't registered
+# * PROUT REGISTER AND MOVE
+# * PROUT REGISTER AND MOVE with a RESERVATION KEY that isn't registered
+# * multiple initiators
+
+# Not Tested
+# * PROUT REPLACE LOST RESERVATION (not supported by ctl)
+# * Specify Initiator Ports bit (not supported by ctl)
+# * Activate Persist Through Power Loss bit (not supported by ctl)
+# * All Target Ports bit (not supported by ctl)
+
+RESERVATION_KEY=0xdeadbeef1a7ebabe
+
+atf_test_case prin_read_full_status_empty cleanup
+prin_read_full_status_empty_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION IN with the READ FULL STATUS 
service action, with no status descriptors"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prin_read_full_status_empty_body()
+{
+       create_ramdisk
+
+       atf_check -o match:"No full status descriptors" sg_persist -ns /dev/$dev
+}
+prin_read_full_status_empty_cleanup()
+{
+       cleanup
+}
+
+atf_test_case prin_read_keys_empty cleanup
+prin_read_keys_empty_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION IN with the READ KEYS service 
action, with no registered keys"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prin_read_keys_empty_body()
+{
+       create_ramdisk
+
+       atf_check -o match:"there are NO registered reservation keys" 
sg_persist -nk /dev/$dev
+}
+prin_read_keys_empty_cleanup()
+{
+       cleanup
+}
+
+atf_test_case prin_read_reservation_empty cleanup
+prin_read_reservation_empty_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION IN with the READ RESERVATION 
service action, with no reservations"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prin_read_reservation_empty_body()
+{
+       create_ramdisk
+
+       atf_check -o match:"there is NO reservation held" sg_persist -nr 
/dev/$dev
+}
+prin_read_reservation_empty_cleanup()
+{
+       cleanup
+}
+
+atf_test_case prin_report_capabilities cleanup
+prin_report_capabilities_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION IN with the REPORT CAPABILITIES 
service action"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prin_report_capabilities_body()
+{
+       create_ramdisk
+
+       cat > expected <<HERE
+Report capabilities response:
+  Replace Lost Reservation Capable(RLR_C): 0
+  Compatible Reservation Handling(CRH): 1
+  Specify Initiator Ports Capable(SIP_C): 0
+  All Target Ports Capable(ATP_C): 0
+  Persist Through Power Loss Capable(PTPL_C): 0
+  Type Mask Valid(TMV): 1
+  Allow Commands: 5
+  Persist Through Power Loss Active(PTPL_A): 0
+    Support indicated in Type mask:
+      Write Exclusive, all registrants: 1
+      Exclusive Access, registrants only: 1
+      Write Exclusive, registrants only: 1
+      Exclusive Access: 1
+      Write Exclusive: 1
+      Exclusive Access, all registrants: 1
+HERE
+       atf_check -o file:expected sg_persist -nc /dev/$dev
+}
+prin_report_capabilities_cleanup()
+{
+       cleanup
+}
+
+atf_test_case prout_clear cleanup
+prout_clear_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION OUT with the CLEAR service 
action"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prout_clear_body()
+{
+       create_ramdisk
+
+       # First register a key
+       atf_check sg_persist -n --out --param-rk=0 
--param-sark=$RESERVATION_KEY -G /dev/$dev
+
+       # Then make a reservation using that key
+       atf_check sg_persist -n --out --param-rk=$RESERVATION_KEY --reserve 
--prout-type=8 /dev/$dev
+
+       # Now, clear all reservations and registrations
+       atf_check sg_persist -n --out --param-rk=$RESERVATION_KEY --clear 
/dev/$dev
+
+       # Finally, check that all reservations and keys are gone
+       atf_check -o match:"there is NO reservation held" sg_persist -nr 
/dev/$dev
+       atf_check -o match:"there are NO registered reservation keys" 
sg_persist -nk /dev/$dev
+}
+prout_clear_cleanup()
+{
+       cleanup
+}
+
+
+atf_test_case prout_register cleanup
+prout_register_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION OUT with the REGISTER service 
action"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prout_register_body()
+{
+       create_ramdisk
+       atf_check sg_persist -n --out --param-rk=0 
--param-sark=$RESERVATION_KEY -G /dev/$dev
+       atf_check -o match:$RESERVATION_KEY sg_persist -nk /dev/$dev
+}
+prout_register_cleanup()
+{
+       cleanup
+}
+
+atf_test_case prout_register_duplicate cleanup
+prout_register_duplicate_head()
+{
+       atf_set "descr" "attempting to register a key twice should fail"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prout_register_duplicate_body()
+{
+       create_ramdisk
+       atf_check sg_persist -n --out --param-rk=0 
--param-sark=$RESERVATION_KEY -G /dev/$dev
+       atf_check -s exit:24 -e match:"Reservation conflict" sg_persist -n 
--out --param-rk=0 --param-sark=$RESERVATION_KEY -G /dev/$dev
+       atf_check -o match:$RESERVATION_KEY sg_persist -nk /dev/$dev
+}
+prout_register_duplicate_cleanup()
+{
+       cleanup
+}
+
+atf_test_case prout_register_huge_cdb cleanup
+prout_register_huge_cdb_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION OUT with an enormous CDB size 
should not cause trouble"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prout_register_huge_cdb_body()
+{
+       create_ramdisk
+
+       atf_check -s exit:1 $(atf_get_srcdir)/prout_register_huge_cdb $LUN
+}
+prout_register_huge_cdb_cleanup()
+{
+       cleanup
+}
+
+atf_test_case prout_register_unregister cleanup
+prout_register_unregister_head()
+{
+       atf_set "descr" "use PERSISTENT RESERVATION OUT with the REGISTER 
service action to remove a prior registration"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prout_register_unregister_body()
+{
+       create_ramdisk
+       # First register a key
+       atf_check sg_persist -n --out --param-rk=0 
--param-sark=$RESERVATION_KEY -G /dev/$dev
+       # Then unregister it
+       atf_check sg_persist -n --out --param-sark=0 
--param-rk=$RESERVATION_KEY -G /dev/$dev
+       # Finally, check that no keys are registered
+       atf_check -o match:"there are NO registered reservation keys" 
sg_persist -nk /dev/$dev
+}
+prout_register_unregister_cleanup()
+{
+       cleanup
+}
+
+atf_test_case prout_release cleanup
+prout_release_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION OUT with the RESERVE service 
action"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prout_release_body()
+{
+       create_ramdisk
+
+       # First register a key
+       atf_check sg_persist -n --out --param-rk=0 
--param-sark=$RESERVATION_KEY -G /dev/$dev
+
+       # Then make a reservation using that key
+       atf_check sg_persist -n --out --param-rk=$RESERVATION_KEY --reserve 
--prout-type=8 /dev/$dev
+       atf_check sg_persist -n --out --param-rk=$RESERVATION_KEY 
--prout-type=8 --release /dev/$dev
+
+       # Now check that the reservation is released
+       atf_check -o match:"there is NO reservation held" sg_persist -nr 
/dev/$dev
+       # But the registration shouldn't be.
+       atf_check -o match:$RESERVATION_KEY sg_persist -nk /dev/$dev
+}
+prout_release_cleanup()
+{
+       cleanup
+}
+
+
+atf_test_case prout_reserve cleanup
+prout_reserve_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION OUT with the RESERVE service 
action"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist ctladm
+}
+prout_reserve_body()
+{
+       create_ramdisk
+       # First register a key
+       atf_check sg_persist -n --out --param-rk=0 
--param-sark=$RESERVATION_KEY -G /dev/$dev
+       # Then make a reservation using that key
+       atf_check sg_persist -n --out --param-rk=$RESERVATION_KEY --reserve 
--prout-type=8 /dev/$dev
+       # Finally, check that the reservation is correct
+       cat > expected <<HERE
+  PR generation=0x1
+    Key=0xdeadbeef1a7ebabe
+      All target ports bit clear
+      Relative port address: 0x0
+      << Reservation holder >>
+      scope: LU_SCOPE,  type: Exclusive Access, all registrants
+      Transport Id of initiator:
+        Parallel SCSI initiator SCSI address: 0x1
+        relative port number (of corresponding target): 0x0
+HERE
+       atf_check -o file:expected sg_persist -ns /dev/$dev
+}
+prout_reserve_cleanup()
+{
+       cleanup
+}
+
+atf_test_case prout_reserve_bad_scope cleanup
+prout_reserve_bad_scope_head()
+{
+       atf_set "descr" "PERSISTENT RESERVATION OUT will be rejected with an 
unknown scope field"
+       atf_set "require.user" "root"
+       atf_set "require.progs" sg_persist camcontrol ctladm
+}
+prout_reserve_bad_scope_body()
+{
+       create_ramdisk
+       # First register a key
+       atf_check sg_persist -n --out --param-rk=0 
--param-sark=$RESERVATION_KEY -G /dev/$dev
+
+       # Then make a reservation using that key
+       atf_check -s exit:1 -e match:"ILLEGAL REQUEST asc:24,0 .Invalid field 
in CDB." camcontrol persist $dev -o reserve -k $RESERVATION_KEY -T read_shared 
-s 15 -v
+
+       # Finally, check that nothing has been reserved
+       atf_check -o match:"there is NO reservation held" sg_persist -nr 
/dev/$dev
+}
+prout_reserve_bad_scope_cleanup()
+{
+       cleanup
+}
+
+
+atf_init_test_cases()
+{
+       atf_add_test_case prin_read_full_status_empty
+       atf_add_test_case prin_read_keys_empty
+       atf_add_test_case prin_read_reservation_empty
+       atf_add_test_case prin_report_capabilities
+       atf_add_test_case prout_clear
+       atf_add_test_case prout_register
+       atf_add_test_case prout_register_duplicate
+       atf_add_test_case prout_register_huge_cdb
+       atf_add_test_case prout_register_unregister
+       atf_add_test_case prout_release
+       atf_add_test_case prout_reserve
+       atf_add_test_case prout_reserve_bad_scope
+}
diff --git a/tests/sys/cam/ctl/prout_register_huge_cdb.c 
b/tests/sys/cam/ctl/prout_register_huge_cdb.c
new file mode 100644
index 000000000000..f57a6abfadd6
--- /dev/null
+++ b/tests/sys/cam/ctl/prout_register_huge_cdb.c
@@ -0,0 +1,88 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 ConnectWise
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS DOCUMENTATION IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Helper that sends a PERSISTENT RESERVATION OUT command to CTL with a
+ * ridiculously huge size for the length of the CDB.  This is not possible with
+ * ctladm, for good reason.
+ */
+#include <camlib.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include <cam/scsi/scsi_message.h>
+#include <cam/ctl/ctl_io.h>
+#include <cam/ctl/ctl.h>
+#include <cam/ctl/ctl_ioctl.h>
+#include <cam/ctl/ctl_util.h>
+
+int
+main(int argc, char **argv)
+{
+       union ctl_io *io;
+       int fd = open("/dev/cam/ctl", O_RDWR);
+       int r;
+       uint32_t targ_port;
+
+       if (argc < 2)
+               errx(2, "usage: prout_register_huge_cdb <target_port>\n");
+
+       targ_port = strtoul(argv[1], NULL, 10);
+
+       io = calloc(1, sizeof(*io));
+       io->io_hdr.nexus.initid = 7;    /* 7 is ctladm's default initiator id */
+       io->io_hdr.nexus.targ_port = targ_port;
+       io->io_hdr.nexus.targ_mapped_lun = 0;
+       io->io_hdr.nexus.targ_lun = 0;
+       io->io_hdr.io_type = CTL_IO_SCSI;
+       io->taskio.tag_type = CTL_TAG_UNTAGGED;
+       uint8_t cdb[32] = {};
+       // ctl_persistent_reserve_out// 5f 00
+       cdb[0] = 0x5f;
+       cdb[1] = 0x00;
+       struct scsi_per_res_out *cdb_ = ( struct scsi_per_res_out *)cdb;
+       // Claim an enormous size of the CDB, but don't actually alloc it all.
+       cdb_->length[0] = 0xff;
+       cdb_->length[1] = 0xff;
+       cdb_->length[2] = 0xff;
+       cdb_->length[3] = 0xff;
+       io->scsiio.cdb_len = sizeof(cdb);
+       memcpy(io->scsiio.cdb, cdb, sizeof(cdb));
+       io->io_hdr.flags |= CTL_FLAG_DATA_IN;
+       r = ioctl(fd, CTL_IO, io);
+       if (r == -1)
+               err(1, "ioctl");
+       if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+               return (0);
+       } else {
+               return (1);
+       }
+}

Reply via email to