* [PATCH v3 1/4] ata: libata-scsi: reject reserved bits in 32-byte passthru
2026-11-12 14:01 [PATCH v3 0/4] ata: libata-scsi: fix ATA PASS-THROUGH(32) handling shao.mingyin
@ 2026-11-12 14:01 ` shao.mingyin
2026-11-13 0:20 ` Damien Le Moal
2026-11-12 14:01 ` [PATCH v3 2/4] ata: libata-scsi: reject unsupported ATA-32 service actions shao.mingyin
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: shao.mingyin @ 2026-11-12 14:01 UTC (permalink / raw)
To: dlemoal, cassel, linux-ide; +Cc: linux-kernel, stable
From: Shao Mingyin <shao.mingyin@zte.com.cn>
ATA PASS-THROUGH(32) has no MULTIPLE_COUNT field. Byte 1 is CONTROL,
and bits 7:5 are reserved or obsolete. Interpreting these bits as a
multiple count can issue spurious count mismatch warnings for multiple
commands instead of rejecting the invalid CDB.
Reject nonzero byte 1 bits 7:5 with field pointer 1. Skip the CDB count
comparison for the 32-byte form while retaining the device multiple-mode
check. Also point to byte 10 for invalid PROTOCOL. The 12- and 16-byte
MULTIPLE_COUNT checks remain unchanged.
Fixes: b1ffbf854e08 ("libata: Support for an ATA PASS-THROUGH(32) command.")
Suggested-by: Niklas Cassel <cassel@kernel.org>
Signed-off-by: Shao Mingyin <shao.mingyin@zte.com.cn>
---
Changes in v3 (Damien Le Moal):
- Reject nonzero reserved/obsolete bits 7:5 of byte 1 with fp=1.
- Retain the short-CDB count and device multiple-mode checks.
- Update the subject, description and CONTROL-byte test expectations.
v2: https://lore.kernel.org/all/20261111223129430zClEJF8TDgu_h8ZYM843Q@zte.com.cn/
Validation on x86-64 with GCC 13 and UBSAN: a userspace harness compiles
ata_scsi_pass_thru() verbatim with SCSI/device stubs. All 256 CONTROL
values are checked for both non-multiple and multiple commands. Nonzero
bits 7:5 are rejected with field pointer 1; values with those bits clear
are accepted without count warnings. The device multiple-mode check and
12-/16-byte count checks still pass. Field pointers are observed helper
arguments; sense construction and device access use stubs.
drivers/ata/libata-scsi.c | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index a61fb03312793ca94d81d8061a16c66e786c4cf3..0bc147cf4c0fa50c7d5b84029b7fb1a548c6f1a3 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -3388,7 +3388,7 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
tf->protocol = ata_scsi_map_proto(cdb[1 + cdb_offset]);
if (tf->protocol == ATA_PROT_UNKNOWN) {
- fp = 1;
+ fp = 1 + cdb_offset;
goto invalid_fld;
}
@@ -3573,27 +3573,32 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
goto invalid_fld;
}
- /* sanity check for pio multi commands */
+ /* Sanity check for pio multi commands (not supported with ATA 32). */
+ if (cdb[0] == VARIABLE_LENGTH_CMD && (cdb[1] & 0xe0)) {
+ fp = 1;
+ goto invalid_fld;
+ }
+
if ((cdb[1] & 0xe0) && !is_multi_taskfile(tf)) {
fp = 1;
goto invalid_fld;
}
if (is_multi_taskfile(tf)) {
- unsigned int multi_count = 1 << (cdb[1] >> 5);
-
if (!dev->multi_count) {
ata_scsi_set_sense(dev, scmd, ABORTED_COMMAND,
NO_ADDITIONAL_SENSE_INFORMATION);
return 1;
}
- /* compare the passed through multi_count
- * with the cached multi_count of libata
- */
- if (multi_count != dev->multi_count)
- ata_dev_warn(dev, "invalid multi_count %u ignored\n",
- multi_count);
+ if (cdb[0] != VARIABLE_LENGTH_CMD) {
+ unsigned int multi_count = 1 << (cdb[1] >> 5);
+
+ /* Compare with the cached multiple count of libata. */
+ if (multi_count != dev->multi_count)
+ ata_dev_warn(dev, "invalid multi_count %u ignored\n",
+ multi_count);
+ }
}
/*
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH v3 1/4] ata: libata-scsi: reject reserved bits in 32-byte passthru
2026-11-12 14:01 ` [PATCH v3 1/4] ata: libata-scsi: reject reserved bits in 32-byte passthru shao.mingyin
@ 2026-11-13 0:20 ` Damien Le Moal
0 siblings, 0 replies; 6+ messages in thread
From: Damien Le Moal @ 2026-11-13 0:20 UTC (permalink / raw)
To: shao.mingyin, cassel, linux-ide; +Cc: linux-kernel, stable
On 11/12/26 23:01, shao.mingyin@zte.com.cn wrote:
> From: Shao Mingyin <shao.mingyin@zte.com.cn>
>
> ATA PASS-THROUGH(32) has no MULTIPLE_COUNT field. Byte 1 is CONTROL,
> and bits 7:5 are reserved or obsolete. Interpreting these bits as a
> multiple count can issue spurious count mismatch warnings for multiple
> commands instead of rejecting the invalid CDB.
>
> Reject nonzero byte 1 bits 7:5 with field pointer 1. Skip the CDB count
> comparison for the 32-byte form while retaining the device multiple-mode
> check. Also point to byte 10 for invalid PROTOCOL. The 12- and 16-byte
> MULTIPLE_COUNT checks remain unchanged.
>
> Fixes: b1ffbf854e08 ("libata: Support for an ATA PASS-THROUGH(32) command.")
> Suggested-by: Niklas Cassel <cassel@kernel.org>
> Signed-off-by: Shao Mingyin <shao.mingyin@zte.com.cn>
One nit below. With that fixed,
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
> + /* Compare with the cached multiple count of libata. */
> + if (multi_count != dev->multi_count)
> + ata_dev_warn(dev, "invalid multi_count %u ignored\n",
Long line. Please split it after "dev,".
> + multi_count);
> + }
> }
>
> /*
--
Damien Le Moal
Western Digital Research
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 2/4] ata: libata-scsi: reject unsupported ATA-32 service actions
2026-11-12 14:01 [PATCH v3 0/4] ata: libata-scsi: fix ATA PASS-THROUGH(32) handling shao.mingyin
2026-11-12 14:01 ` [PATCH v3 1/4] ata: libata-scsi: reject reserved bits in 32-byte passthru shao.mingyin
@ 2026-11-12 14:01 ` shao.mingyin
2026-11-12 14:01 ` [PATCH v3 3/4] ata: libata-scsi: generate SAT sense data for ATA PASS-THROUGH(32) shao.mingyin
2026-11-12 14:01 ` [PATCH v3 4/4] ata: libata-scsi: fix remaining passthru field pointers shao.mingyin
3 siblings, 0 replies; 6+ messages in thread
From: shao.mingyin @ 2026-11-12 14:01 UTC (permalink / raw)
To: dlemoal, cassel, linux-ide; +Cc: linux-kernel, stable
From: Shao Mingyin <shao.mingyin@zte.com.cn>
ata_scsi_var_len_cdb_xlat() returns 1 for an unsupported service action
without setting sense data. ata_scsi_translate() then completes the
command with the SCSI result still zero, reporting SAM_STAT_GOOD even
though no ATA command was issued and no data was transferred.
Set ILLEGAL REQUEST/INVALID FIELD IN CDB before returning, with the
field pointer referring to the SERVICE ACTION field at byte 8.
Fixes: b1ffbf854e08 ("libata: Support for an ATA PASS-THROUGH(32) command.")
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Shao Mingyin <shao.mingyin@zte.com.cn>
---
Changes in v3:
- Code unchanged; retain the Damien Le Moal Reviewed-by tag.
Previous version: https://lore.kernel.org/all/20261111223130666omUZ0JoWtfZjRFRrWPse8@zte.com.cn/
Changes in v2:
- No code changes; regenerate against the series base and keep the
Reviewed-by tag.
v1: https://lore.kernel.org/all/20261104221102824qAKUWNkiSgZ1yRnQM8hI9@zte.com.cn/
Validation on x86-64 with GCC 13 and UBSAN: a userspace harness compiled
the translation functions verbatim with SCSI/device stubs. For service
action 0x1234, completion changed from result 0 to ILLEGAL REQUEST,
INVALID FIELD IN CDB; the invalid-field helper received field pointer 8.
A valid ATA_32 service action remained accepted.
drivers/ata/libata-scsi.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index 0bc147cf4c0fa50c7d5b84029b7fb1a548c6f1a3..2491e90fb1a5ec6e83234a07eef5342055875937 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -5032,7 +5032,13 @@ static unsigned int ata_scsi_var_len_cdb_xlat(struct ata_queued_cmd *qc)
if (sa == ATA_32)
return ata_scsi_pass_thru(qc);
- /* unsupported service action */
+ /*
+ * Unsupported service action. Set the sense data explicitly, as
+ * returning 1 without it makes ata_scsi_translate() complete the
+ * command with the SCSI result still zero, that is, with
+ * SAM_STAT_GOOD.
+ */
+ ata_scsi_set_invalid_field(qc->dev, scmd, 8, 0xff);
return 1;
}
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v3 3/4] ata: libata-scsi: generate SAT sense data for ATA PASS-THROUGH(32)
2026-11-12 14:01 [PATCH v3 0/4] ata: libata-scsi: fix ATA PASS-THROUGH(32) handling shao.mingyin
2026-11-12 14:01 ` [PATCH v3 1/4] ata: libata-scsi: reject reserved bits in 32-byte passthru shao.mingyin
2026-11-12 14:01 ` [PATCH v3 2/4] ata: libata-scsi: reject unsupported ATA-32 service actions shao.mingyin
@ 2026-11-12 14:01 ` shao.mingyin
2026-11-12 14:01 ` [PATCH v3 4/4] ata: libata-scsi: fix remaining passthru field pointers shao.mingyin
3 siblings, 0 replies; 6+ messages in thread
From: shao.mingyin @ 2026-11-12 14:01 UTC (permalink / raw)
To: dlemoal, cassel, linux-ide; +Cc: linux-kernel, stable
From: Shao Mingyin <shao.mingyin@zte.com.cn>
ata_scsi_qc_complete() only recognizes ATA PASS-THROUGH(12) and (16).
The 32-byte form takes the ordinary completion path, so CK_COND is
ignored and errors lack the ATA Status Return descriptor. Successful
commands with CK_COND set therefore complete GOOD instead of returning
CHECK CONDITION with the ATA register values.
Recognize ATA PASS-THROUGH(32) by its service action and read CK_COND
from byte 11 instead of byte 2, which belongs to the variable-length
CDB header.
Fixes: b1ffbf854e08 ("libata: Support for an ATA PASS-THROUGH(32) command.")
Cc: stable@vger.kernel.org
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Shao Mingyin <shao.mingyin@zte.com.cn>
---
Changes in v3:
- Code unchanged; retain the Damien Le Moal Reviewed-by tag.
Previous version: https://lore.kernel.org/all/202611112231319337i08AK7ZzIXm9DYFhmtXx@zte.com.cn/
Changes in v2 (Niklas Cassel):
- Move the unrelated invalid-PROTOCOL hunk to patch 1; this patch only
changes the completion path.
- Regenerate with git format-patch so the index matches the diff.
- Keep the Reviewed-by tag; apart from the moved hunk this patch is
unchanged from v1.
v1: https://lore.kernel.org/all/202611042211545962bhKeo8yjWPGFgya4g6LG@zte.com.cn/
Validation on x86-64 with GCC 13 and UBSAN: a userspace harness compiled
ata_scsi_qc_complete() verbatim with sense/completion stubs. For ATA_32,
CK_COND in byte 11 now selected pass-through sense and CHECK CONDITION;
CK_COND clear remained GOOD. Error and preexisting-sense paths selected
the pass-through helpers, and byte 2 did not act as CK_COND. The short
CDB paths and unsupported service-action control were also checked.
drivers/ata/libata-scsi.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index 2491e90fb1a5ec6e83234a07eef5342055875937..8ee68937e884369beb4d7ad218612d28c5a080ff 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -1888,9 +1888,12 @@ static void ata_scsi_qc_complete(struct ata_queued_cmd *qc)
struct ata_link *link = qc->dev->link;
struct scsi_cmnd *cmd = qc->scsicmd;
u8 *cdb = cmd->cmnd;
+ u16 cdb_offset = cdb[0] == VARIABLE_LENGTH_CMD ? 9 : 0;
bool have_sense = qc->flags & ATA_QCFLAG_SENSE_VALID;
- bool is_ata_passthru = cdb[0] == ATA_16 || cdb[0] == ATA_12;
- bool is_ck_cond_request = cdb[2] & 0x20;
+ bool is_ata_passthru = cdb[0] == ATA_16 || cdb[0] == ATA_12 ||
+ (cdb[0] == VARIABLE_LENGTH_CMD &&
+ get_unaligned_be16(&cdb[8]) == ATA_32);
+ bool is_ck_cond_request = cdb[2 + cdb_offset] & 0x20;
bool is_error = qc->err_mask != 0;
/* For ATA pass thru (SAT) commands, generate a sense block if
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v3 4/4] ata: libata-scsi: fix remaining passthru field pointers
2026-11-12 14:01 [PATCH v3 0/4] ata: libata-scsi: fix ATA PASS-THROUGH(32) handling shao.mingyin
` (2 preceding siblings ...)
2026-11-12 14:01 ` [PATCH v3 3/4] ata: libata-scsi: generate SAT sense data for ATA PASS-THROUGH(32) shao.mingyin
@ 2026-11-12 14:01 ` shao.mingyin
3 siblings, 0 replies; 6+ messages in thread
From: shao.mingyin @ 2026-11-12 14:01 UTC (permalink / raw)
To: dlemoal, cassel, linux-ide; +Cc: linux-kernel, stable
From: Shao Mingyin <shao.mingyin@zte.com.cn>
Several error paths in ata_scsi_pass_thru() report field pointers for
the 12- or 16-byte CDB when rejecting a 32-byte CDB. Initiators then
receive INVALID FIELD IN CDB sense data pointing at an unrelated byte.
READ/WRITE LONG also reports PROTOCOL when its sector count is invalid.
Use the PROTOCOL offset and record the FEATURES, SECTOR COUNT and
COMMAND offsets for each CDB format. Report SECTOR COUNT separately
from PROTOCOL when validating READ/WRITE LONG.
Fixes: b1ffbf854e08 ("libata: Support for an ATA PASS-THROUGH(32) command.")
Suggested-by: Niklas Cassel <cassel@kernel.org>
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Shao Mingyin <shao.mingyin@zte.com.cn>
---
Changes in v3:
- Code unchanged; retain the Damien Le Moal Reviewed-by tag.
Previous version: https://lore.kernel.org/all/20261111223133188VjHq0FbmnzuEZWakS-EYD@zte.com.cn/
Changes in v2 (Niklas Cassel):
- Add a separate patch for the remaining PROTOCOL, FEATURES, COMMAND
and READ/WRITE LONG sector-count field pointers.
v1: https://lore.kernel.org/all/20261104220832716LXKf7OaS9dt1VJ8Q959AG@zte.com.cn/
Validation on x86-64 with GCC 13 and UBSAN: a userspace harness compiled
the translation functions verbatim with SCSI/device stubs. The rejected
32-byte DMA/NCQ and READ/WRITE LONG protocols passed field pointer 10 to
the invalid-field helper, SET FEATURES passed 21, and TPM passed 25.
Invalid READ/WRITE LONG sector counts passed 4, 6 and 23 for 12-, 16-
and 32-byte CDBs. These are helper arguments observed in the harness.
drivers/ata/libata-scsi.c | 26 ++++++++++++++++++++------
1 file changed, 20 insertions(+), 6 deletions(-)
diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index 8ee68937e884369beb4d7ad218612d28c5a080ff..c59e6567a108bbb353c08e921228eadbd231da63 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -3383,6 +3383,7 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
struct ata_device *dev = qc->dev;
const u8 *cdb = scmd->cmnd;
u16 fp;
+ u8 feature_offset, nsect_offset, command_offset;
u16 cdb_offset = 0;
/* 7Fh variable length cmd means a ata pass-thru(32) */
@@ -3443,6 +3444,9 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
tf->lbah = cdb[12];
tf->device = cdb[13];
tf->command = cdb[14];
+ feature_offset = 4;
+ nsect_offset = 6;
+ command_offset = 14;
break;
case ATA_12:
/*
@@ -3457,6 +3461,9 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
tf->lbah = cdb[7];
tf->device = cdb[8];
tf->command = cdb[9];
+ feature_offset = 3;
+ nsect_offset = 4;
+ command_offset = 9;
break;
default:
/*
@@ -3482,6 +3489,9 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
tf->device = cdb[24];
tf->command = cdb[25];
tf->auxiliary = get_unaligned_be32(&cdb[28]);
+ feature_offset = 21;
+ nsect_offset = 23;
+ command_offset = 25;
break;
}
@@ -3499,8 +3509,12 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
case ATA_CMD_READ_LONG_ONCE:
case ATA_CMD_WRITE_LONG:
case ATA_CMD_WRITE_LONG_ONCE:
- if (tf->protocol != ATA_PROT_PIO || tf->nsect != 1) {
- fp = 1;
+ if (tf->protocol != ATA_PROT_PIO) {
+ fp = 1 + cdb_offset;
+ goto invalid_fld;
+ }
+ if (tf->nsect != 1) {
+ fp = nsect_offset;
goto invalid_fld;
}
qc->sect_size = scsi_bufflen(scmd);
@@ -3566,13 +3580,13 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
/* We may not issue DMA commands if no DMA mode is set */
if (tf->protocol == ATA_PROT_DMA && !ata_dma_enabled(dev)) {
- fp = 1;
+ fp = 1 + cdb_offset;
goto invalid_fld;
}
/* We may not issue NCQ commands to devices not supporting NCQ */
if (ata_is_ncq(tf->protocol) && !ata_ncq_enabled(dev)) {
- fp = 1;
+ fp = 1 + cdb_offset;
goto invalid_fld;
}
@@ -3613,7 +3627,7 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
*/
if (tf->command == ATA_CMD_SET_FEATURES &&
tf->feature == SETFEATURES_XFER) {
- fp = (cdb[0] == ATA_16) ? 4 : 3;
+ fp = feature_offset;
goto invalid_fld;
}
@@ -3633,7 +3647,7 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
* can turn off TC features of their system.
*/
if (tf->command >= 0x5C && tf->command <= 0x5F && !libata_allow_tpm) {
- fp = (cdb[0] == ATA_16) ? 14 : 9;
+ fp = command_offset;
goto invalid_fld;
}
^ permalink raw reply [flat|nested] 6+ messages in thread