git: 6dd86310e54d - main - CTL: add ATF tests for REPORT SUPPORTED OPCODES

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Tue, 14 Apr 2026 17:37:23 UTC
The branch main has been updated by asomers:

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

commit 6dd86310e54d3f2dd9f688670913b9176176246c
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2026-04-14 17:35:54 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2026-04-14 17:36:07 +0000

    CTL: add ATF tests for REPORT SUPPORTED OPCODES
    
    This includes a regression test for CVE-2024-42416
    
    MFC after:      2 weeks
    Sponsored by:   ConnectWise
    Reviewed by:    emaste
    Differential Revision: https://reviews.freebsd.org/D46613
---
 tests/sys/cam/ctl/Makefile                  |   2 +
 tests/sys/cam/ctl/all-supported-opcodes.txt |  39 +++++
 tests/sys/cam/ctl/opcodes.sh                | 241 ++++++++++++++++++++++++++++
 3 files changed, 282 insertions(+)

diff --git a/tests/sys/cam/ctl/Makefile b/tests/sys/cam/ctl/Makefile
index 05f0831fc8b0..4707a2f4b357 100644
--- a/tests/sys/cam/ctl/Makefile
+++ b/tests/sys/cam/ctl/Makefile
@@ -3,8 +3,10 @@ PACKAGE=	tests
 TESTSDIR=	${TESTSBASE}/sys/cam/ctl
 BINDIR=${TESTSDIR}
 
+${PACKAGE}FILES+=	all-supported-opcodes.txt
 ${PACKAGE}FILES+=	ctl.subr
 
+ATF_TESTS_SH+=	opcodes
 ATF_TESTS_SH+=	persist
 ATF_TESTS_SH+=	prevent
 ATF_TESTS_SH+=	read_buffer
diff --git a/tests/sys/cam/ctl/all-supported-opcodes.txt b/tests/sys/cam/ctl/all-supported-opcodes.txt
new file mode 100644
index 000000000000..d1725b5a7c80
--- /dev/null
+++ b/tests/sys/cam/ctl/all-supported-opcodes.txt
@@ -0,0 +1,39 @@
+ 00     00 00 02 60 00 00 00 00  00 00 00 06 03 00 00 00
+ 10     00 00 00 06 04 00 00 00  00 00 00 06 08 00 00 00
+ 20     00 00 00 06 0a 00 00 00  00 00 00 06 12 00 00 00
+ 30     00 00 00 06 15 00 00 00  00 00 00 06 16 00 00 00
+ 40     00 00 00 06 17 00 00 00  00 00 00 06 1a 00 00 00
+ 50     00 00 00 06 1b 00 00 00  00 00 00 06 1e 00 00 00
+ 60     00 00 00 06 25 00 00 00  00 00 00 0a 28 00 00 00
+ 70     00 00 00 0a 2a 00 00 00  00 00 00 0a 2e 00 00 00
+ 80     00 00 00 0a 2f 00 00 00  00 00 00 0a 35 00 00 00
+ 90     00 00 00 0a 37 00 00 00  00 00 00 0a 3b 00 00 02
+ a0     00 01 00 0a 3c 00 00 02  00 01 00 0a 3c 00 00 03
+ b0     00 01 00 0a 3c 00 00 0b  00 01 00 0a 41 00 00 00
+ c0     00 00 00 0a 42 00 00 00  00 00 00 0a 4d 00 00 00
+ d0     00 00 00 0a 55 00 00 00  00 00 00 0a 56 00 00 00
+ e0     00 00 00 0a 57 00 00 00  00 00 00 0a 5a 00 00 00
+ f0     00 00 00 0a 5e 00 00 00  00 01 00 0a 5e 00 00 01
+ 100    00 01 00 0a 5e 00 00 02  00 01 00 0a 5e 00 00 03
+ 110    00 01 00 0a 5f 00 00 00  00 01 00 0a 5f 00 00 01
+ 120    00 01 00 0a 5f 00 00 02  00 01 00 0a 5f 00 00 03
+ 130    00 01 00 0a 5f 00 00 04  00 01 00 0a 5f 00 00 05
+ 140    00 01 00 0a 5f 00 00 06  00 01 00 0a 83 00 00 00
+ 150    00 01 00 10 83 00 00 01  00 01 00 10 83 00 00 10
+ 160    00 01 00 10 83 00 00 11  00 01 00 10 83 00 00 1c
+ 170    00 01 00 10 84 00 00 00  00 01 00 10 84 00 00 03
+ 180    00 01 00 10 84 00 00 04  00 01 00 10 84 00 00 05
+ 190    00 01 00 10 84 00 00 07  00 01 00 10 84 00 00 08
+ 1a0    00 01 00 10 88 00 00 00  00 00 00 10 89 00 00 00
+ 1b0    00 00 00 10 8a 00 00 00  00 00 00 10 8e 00 00 00
+ 1c0    00 00 00 10 8f 00 00 00  00 00 00 10 91 00 00 00
+ 1d0    00 00 00 10 93 00 00 00  00 00 00 10 9b 00 00 02
+ 1e0    00 01 00 10 9b 00 00 03  00 01 00 10 9b 00 00 0b
+ 1f0    00 01 00 10 9c 00 00 00  00 00 00 10 9e 00 00 10
+ 200    00 01 00 10 9e 00 00 12  00 01 00 10 a0 00 00 00
+ 210    00 00 00 0c a3 00 00 05  00 01 00 0c a3 00 00 0a
+ 220    00 01 00 0c a3 00 00 0c  00 01 00 0c a3 00 00 0d
+ 230    00 01 00 0c a3 00 00 0f  00 01 00 0c a8 00 00 00
+ 240    00 00 00 0c aa 00 00 00  00 00 00 0c ae 00 00 00
+ 250    00 00 00 0c af 00 00 00  00 00 00 0c b7 00 00 00
+ 260    00 00 00 0c
diff --git a/tests/sys/cam/ctl/opcodes.sh b/tests/sys/cam/ctl/opcodes.sh
new file mode 100644
index 000000000000..1d558a1095a0
--- /dev/null
+++ b/tests/sys/cam/ctl/opcodes.sh
@@ -0,0 +1,241 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2024 Axcient
+# 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.
+
+# Not Tested
+# * Allocation length, because sg3_utils 1.48 does not provide a way to set it.
+# * RCTD bit, because CTL does not support it.
+
+. $(atf_get_srcdir)/ctl.subr
+
+require_sg_opcodes_version()
+{
+	WANT=$1
+	HAVE=$(sg_opcodes -V 2>&1 | cut -w -f 3)
+	if [ `echo "$HAVE >= $WANT" | bc -l` = 0 ]; then
+		atf_skip "This test requires sg_opcodes $WANT or greater"
+	fi
+}
+
+# Query all supported opcodes.
+# NB: the fixture here may need to change frequently, any time CTL gains
+# support for new opcodes or service actions.
+atf_test_case all_opcodes cleanup
+all_opcodes_head()
+{
+	atf_set "descr" "REPORT SUPPORTED OPCODES can report all supported opcodes"
+	atf_set "require.user" "root"
+	atf_set "require.progs" sg_opcodes
+}
+all_opcodes_body()
+{
+	create_ramdisk
+
+	atf_check -o file:$(atf_get_srcdir)/all-supported-opcodes.txt sg_opcodes -p disk -nH /dev/$dev
+}
+all_opcodes_cleanup()
+{
+	cleanup
+}
+
+# Query support for a single opcode.  The REPORTING OPTIONS field will be 1 and
+# REQUESTED SERVICE ACTION will be zero.
+atf_test_case basic cleanup
+basic_head()
+{
+	atf_set "descr" "REPORT SUPPORTED OPCODES can report a single supported opcode"
+	atf_set "require.user" "root"
+	atf_set "require.progs" sg_opcodes
+}
+basic_body()
+{
+	create_ramdisk
+
+	atf_check -o inline:" 00     00 03 00 0a 28 1a ff ff  ff ff 00 ff ff 07\n" sg_opcodes -o 0x28 -p disk -nH /dev/$dev
+}
+basic_cleanup()
+{
+	cleanup
+}
+
+atf_test_case invalid_rep_opts cleanup
+invalid_rep_opts_head()
+{
+	atf_set "descr" "REPORT SUPPORTED OPCODES will fail gracefully if the REPORTING OPTIONS field is set to an invalid value"
+	atf_set "require.user" "root"
+	atf_set "require.progs" sg_opcodes
+}
+invalid_rep_opts_body()
+{
+	require_sg_opcodes_version 1.03
+	create_ramdisk
+
+	atf_check -o ignore -e ignore -s exit:5 sg_opcodes -o 0x28 -p disk -n --rep-opts=4 /dev/$dev
+}
+invalid_rep_opts_cleanup()
+{
+	cleanup
+}
+
+# Try to query support for an opcode that needs a service action, but without
+# specifying a service action.
+atf_test_case missing_service_action cleanup
+missing_service_action_head()
+{
+	atf_set "descr" "REPORT SUPPORTED OPCODES fails gracefully if the service action is omitted"
+	atf_set "require.user" "root"
+	atf_set "require.progs" sg_opcodes
+}
+missing_service_action_body()
+{
+	create_ramdisk
+
+	atf_check -e ignore -s exit:5 sg_opcodes -o 0x3c -p disk -n /dev/$dev
+}
+missing_service_action_cleanup()
+{
+	cleanup
+}
+
+# Regression test for CVE-2024-42416
+atf_test_case out_of_bounds_service_action cleanup
+out_of_bounds_service_action_head()
+{
+	atf_set "descr" "REPORT SUPPORTED OPCODES fails gracefully if the requested service action is out of bounds"
+	atf_set "require.user" "root"
+	atf_set "require.progs" sg_opcodes
+}
+out_of_bounds_service_action_body()
+{
+	require_sg_opcodes_version 1.03
+	create_ramdisk
+
+	# opcode 0 (Test Unit Ready) does not take service actions
+	# opcode 0x3c (Read Buffer(10)) does take service actions
+	for opcode in 0 0x3c; do
+		for ro in 2 3; do
+			for sa in 32 100 255 256 10000 65535; do
+				atf_check -s exit:5 -o ignore -e ignore sg_opcodes --rep-opts=$ro -o $opcode -s $sa -p disk -nH /dev/$dev
+			done
+		done
+	done
+}
+out_of_bounds_service_action_cleanup()
+{
+	cleanup
+}
+
+# Query support for an opcode that needs a service action
+atf_test_case service_action cleanup
+service_action_head()
+{
+	atf_set "descr" "REPORT SUPPORTED OPCODES can query an opcode that needs a service action"
+	atf_set "require.user" "root"
+	atf_set "require.progs" sg_opcodes
+}
+service_action_body()
+{
+	create_ramdisk
+
+	atf_check -o inline:" 00     00 03 00 0a 3c 02 00 ff  ff ff ff ff ff 07\n" sg_opcodes -o 0x3c -s 2 -p disk -nH /dev/$dev
+}
+service_action_cleanup()
+{
+	cleanup
+}
+
+# Try to query support for an opcode that does not need a service action, but
+# provide one anyway.
+atf_test_case unexpected_service_action cleanup
+unexpected_service_action_head()
+{
+	atf_set "descr" "REPORT SUPPORTED OPCODES fails gracefully if an extraneous service action is provided"
+	atf_set "require.user" "root"
+	atf_set "require.progs" sg_opcodes
+}
+unexpected_service_action_body()
+{
+	create_ramdisk
+
+	atf_check -e ignore -s exit:5 sg_opcodes -o 0x28 -s 1 -p disk -n /dev/$dev
+}
+unexpected_service_action_cleanup()
+{
+	cleanup
+}
+
+# Try to query support for an opcode that does not need a service action, but
+# provide one anyway.  Set REPORTING OPTIONS to 3.  This requests that the
+# command be reported as unsupported, but REQUEST SUPPORTED OPCODES will return
+# successfully.
+atf_test_case unexpected_service_action_ro3 cleanup
+unexpected_service_action_ro3_head()
+{
+	atf_set "descr" "REPORT SUPPORTED OPCODES fails gracefully if an extraneous service action is provided, using REPORTING OPTIONS 3"
+	atf_set "require.user" "root"
+	atf_set "require.progs" sg_opcodes
+}
+unexpected_service_action_ro3_body()
+{
+	require_sg_opcodes_version 1.03
+	create_ramdisk
+
+	atf_check -e ignore -o inline:" 00     00 01 00 00\n" sg_opcodes --rep-opts=3 -o 0xb7 -s 1 -p disk -nH /dev/$dev
+}
+unexpected_service_action_ro3_cleanup()
+{
+	cleanup
+}
+
+atf_test_case unsupported_opcode cleanup
+unsupported_opcode_head()
+{
+	atf_set "descr" "REPORT SUPPORTED OPCODES can report a single unsupported opcode"
+	atf_set "require.user" "root"
+	atf_set "require.progs" sg_opcodes
+}
+unsupported_opcode_body()
+{
+	create_ramdisk
+
+	atf_check -o inline:" 00     00 01 00 00\n" sg_opcodes -o 1 -p disk -nH /dev/$dev
+}
+unsupported_opcode_cleanup()
+{
+	cleanup
+}
+
+
+atf_init_test_cases()
+{
+	atf_add_test_case all_opcodes
+	atf_add_test_case basic
+	atf_add_test_case invalid_rep_opts
+	atf_add_test_case missing_service_action
+	atf_add_test_case out_of_bounds_service_action
+	atf_add_test_case service_action
+	atf_add_test_case unsupported_opcode
+	atf_add_test_case unexpected_service_action
+	atf_add_test_case unexpected_service_action_ro3
+}