From: Michael Bommarito <michael.bommarito@gmail.com> stable inclusion from stable-v5.10.259 commit cb84e974fb172bc71386289f37b78ea679410b39 category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/16215 CVE: CVE-2026-63887 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i... -------------------------------- [ Upstream commit bf33e01f88388c43e285492a63e539df6ffed64c ] iscsi_encode_text_output() concatenates "key=value\0" records into login->rsp_buf, an 8192-byte kzalloc(MAX_KEY_VALUE_PAIRS) buffer allocated in iscsit_alloc_login_setup_buffer(). The three sprintf() call sites in this function (lines 1398, 1411, 1424 in v7.1-rc2) never check the remaining buffer capacity: *length += sprintf(output_buf, "%s=%s", er->key, er->value); *length += 1; output_buf = textbuf + *length; The 8192-byte ceiling at iscsi_target_check_login_request() bounds the *input* Login PDU payload, but a single PDU can carry up to 2048 minimal four-byte "a=b\0" pairs, each unknown key expanding to a 16-byte "a=NotUnderstood\0" output record via iscsi_add_notunderstood_response(). 2048 * 16 = 32 KiB of output into an 8 KiB buffer, producing a ~24 KiB heap overrun in the kmalloc-8k slab. The fix introduces a static iscsi_encode_text_record() helper that uses snprintf() with a per-call bounds check against the remaining buffer, and threads a u32 textbuf_size parameter through iscsi_encode_text_output(). Both call sites in iscsi_target_handle_csg_zero() (PHASE_SECURITY) and iscsi_target_handle_csg_one() (PHASE_OPERATIONAL) pass MAX_KEY_VALUE_PAIRS. On overflow the encoder logs the condition, calls iscsi_release_extra_responses() to drop queued records, and returns -1; both caller sites now emit ISCSI_STATUS_CLS_INITIATOR_ERR / ISCSI_LOGIN_STATUS_INIT_ERR via iscsit_tx_login_rsp() before returning, so the initiator sees an explicit failed-login response rather than a silent connection drop. (Prior to this patch only the PHASE_OPERATIONAL caller did that; the PHASE_SECURITY caller is converted to the same shape.) Fixes: e48354ce078c ("iscsi-target: Add iSCSI fabric support for target v4.1") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com> Tested-by: John Garry <john.g.garry@oracle.com> Reviewed-by: John Garry <john.g.garry@oracle.com> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com> Signed-off-by: Sasha Levin <sashal@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Lu Chentao <luchentao1@huawei.com> --- drivers/target/iscsi/iscsi_target_nego.c | 7 ++- .../target/iscsi/iscsi_target_parameters.c | 62 ++++++++++++++----- .../target/iscsi/iscsi_target_parameters.h | 2 +- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c index 23ac31753a06..4a613217ae9e 100644 --- a/drivers/target/iscsi/iscsi_target_nego.c +++ b/drivers/target/iscsi/iscsi_target_nego.c @@ -876,14 +876,18 @@ static int iscsi_target_handle_csg_zero( ret = iscsi_encode_text_output( PHASE_SECURITY|PHASE_DECLARATIVE, SENDER_TARGET, login->rsp_buf, &login->rsp_length, + MAX_KEY_VALUE_PAIRS, conn->param_list, conn->tpg->tpg_attrib.login_keys_workaround); - if (ret < 0) + if (ret < 0) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_INIT_ERR); return -1; + } if (!iscsi_check_negotiated_keys(conn->param_list)) { if (conn->tpg->tpg_attrib.authentication && !strncmp(param->value, NONE, 4)) { pr_err("Initiator sent AuthMethod=None but" @@ -947,10 +951,11 @@ static int iscsi_target_handle_csg_one(struct iscsi_conn *conn, struct iscsi_log ret = iscsi_encode_text_output( PHASE_OPERATIONAL|PHASE_DECLARATIVE, SENDER_TARGET, login->rsp_buf, &login->rsp_length, + MAX_KEY_VALUE_PAIRS, conn->param_list, conn->tpg->tpg_attrib.login_keys_workaround); if (ret < 0) { iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR, ISCSI_LOGIN_STATUS_INIT_ERR); diff --git a/drivers/target/iscsi/iscsi_target_parameters.c b/drivers/target/iscsi/iscsi_target_parameters.c index 31cd3c02e517..9c5964a77a45 100644 --- a/drivers/target/iscsi/iscsi_target_parameters.c +++ b/drivers/target/iscsi/iscsi_target_parameters.c @@ -1419,23 +1419,46 @@ int iscsi_decode_text_input( free_buffer: kfree(tmpbuf); return -1; } +/* + * Append "key=value" plus a trailing NUL into @textbuf at *@length. + * Returns 0 on success and advances *@length, or -EMSGSIZE if the + * record (including the NUL) would not fit in the remaining buffer. + */ +static int iscsi_encode_text_record(char *textbuf, u32 *length, + u32 textbuf_size, + const char *key, const char *value) +{ + int n; + u32 avail; + + if (*length >= textbuf_size) + return -EMSGSIZE; + + avail = textbuf_size - *length; + n = snprintf(textbuf + *length, avail, "%s=%s", key, value); + if (n < 0 || (u32)n + 1 > avail) + return -EMSGSIZE; + + *length += n + 1; + return 0; +} + int iscsi_encode_text_output( u8 phase, u8 sender, char *textbuf, u32 *length, + u32 textbuf_size, struct iscsi_param_list *param_list, bool keys_workaround) { - char *output_buf = NULL; struct iscsi_extra_response *er; struct iscsi_param *param; - - output_buf = textbuf + *length; + int ret; if (iscsi_enforce_integrity_rules(phase, param_list) < 0) return -1; list_for_each_entry(param, ¶m_list->param_list, p_list) { @@ -1443,44 +1466,55 @@ int iscsi_encode_text_output( continue; if (IS_PSTATE_ACCEPTOR(param) && !IS_PSTATE_RESPONSE_SENT(param) && !IS_PSTATE_REPLY_OPTIONAL(param) && (param->phase & phase)) { - *length += sprintf(output_buf, "%s=%s", - param->name, param->value); - *length += 1; - output_buf = textbuf + *length; + ret = iscsi_encode_text_record(textbuf, length, + textbuf_size, + param->name, + param->value); + if (ret < 0) + goto err_overflow; SET_PSTATE_RESPONSE_SENT(param); pr_debug("Sending key: %s=%s\n", param->name, param->value); continue; } if (IS_PSTATE_NEGOTIATE(param) && !IS_PSTATE_ACCEPTOR(param) && !IS_PSTATE_PROPOSER(param) && (param->phase & phase)) { - *length += sprintf(output_buf, "%s=%s", - param->name, param->value); - *length += 1; - output_buf = textbuf + *length; + ret = iscsi_encode_text_record(textbuf, length, + textbuf_size, + param->name, + param->value); + if (ret < 0) + goto err_overflow; SET_PSTATE_PROPOSER(param); iscsi_check_proposer_for_optional_reply(param, keys_workaround); pr_debug("Sending key: %s=%s\n", param->name, param->value); } } list_for_each_entry(er, ¶m_list->extra_response_list, er_list) { - *length += sprintf(output_buf, "%s=%s", er->key, er->value); - *length += 1; - output_buf = textbuf + *length; + ret = iscsi_encode_text_record(textbuf, length, textbuf_size, + er->key, er->value); + if (ret < 0) + goto err_overflow; pr_debug("Sending key: %s=%s\n", er->key, er->value); } iscsi_release_extra_responses(param_list); return 0; + +err_overflow: + pr_err("iSCSI login response buffer (%u bytes) exhausted, dropping login.\n", + textbuf_size); + iscsi_release_extra_responses(param_list); + return -1; } int iscsi_check_negotiated_keys(struct iscsi_param_list *param_list) { int ret = 0; diff --git a/drivers/target/iscsi/iscsi_target_parameters.h b/drivers/target/iscsi/iscsi_target_parameters.h index 240c4c4344f6..6d02d19eb8cf 100644 --- a/drivers/target/iscsi/iscsi_target_parameters.h +++ b/drivers/target/iscsi/iscsi_target_parameters.h @@ -44,11 +44,11 @@ extern int iscsi_change_param_value(char *, struct iscsi_param_list *, int); extern void iscsi_release_param_list(struct iscsi_param_list *); extern struct iscsi_param *iscsi_find_param_from_key(char *, struct iscsi_param_list *); extern int iscsi_extract_key_value(char *, char **, char **); extern int iscsi_update_param_value(struct iscsi_param *, char *); extern int iscsi_decode_text_input(u8, u8, char *, u32, struct iscsi_conn *); -extern int iscsi_encode_text_output(u8, u8, char *, u32 *, +extern int iscsi_encode_text_output(u8, u8, char *, u32 *, u32, struct iscsi_param_list *, bool); extern int iscsi_check_negotiated_keys(struct iscsi_param_list *); extern void iscsi_set_connection_parameters(struct iscsi_conn_ops *, struct iscsi_param_list *); extern void iscsi_set_session_parameters(struct iscsi_sess_ops *, -- 2.52.0