Skip to content

Commit e451d1e

Browse files
committed
mi-mctp: better handling for error/unexpected responses
Currently, the mi-mctp transport expects there to be at least a complete header-sized reply - matching the caller's resp->hdr_size value, otherwise we consider this a transport failure. However, Admin command responses have a header that's larger than the generic error response message, so we should be able to handle response messages that are just the small header and no payload. This change reworks the response-buffer handling in the MCTP transport. Rather than storing the payload and MIC into an extra allocated buffer, we just lay out the response iovec directly into the header, payload and MIC buffers, and then handle the short-response cases as needed. This means we can properly extract the error response, rather than just having the transport report a generic "header too small" failure itself. This is at high risk for off-by-one errors, so add tests to cover each of the possible buffer-arrangement cases. Signed-off-by: Jeremy Kerr <[email protected]>
1 parent 502707c commit e451d1e

2 files changed

Lines changed: 154 additions & 23 deletions

File tree

src/nvme/mi-mctp.c

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,9 @@ static int nvme_mi_mctp_submit(struct nvme_mi_ep *ep,
101101
struct nvme_mi_resp *resp)
102102
{
103103
struct nvme_mi_transport_mctp *mctp;
104-
struct iovec req_iov[3], resp_iov[2];
104+
struct iovec req_iov[3], resp_iov[3];
105105
struct msghdr req_msg, resp_msg;
106106
struct sockaddr_mctp addr;
107-
unsigned char *rspbuf;
108107
ssize_t len;
109108
__le32 mic;
110109
int i;
@@ -153,49 +152,77 @@ static int nvme_mi_mctp_submit(struct nvme_mi_ep *ep,
153152
resp_iov[0].iov_base = ((__u8 *)resp->hdr) + 1;
154153
resp_iov[0].iov_len = resp->hdr_len - 1;
155154

156-
/* we use a temporary buffer to receive the response, and then
157-
* split into data & mic. This avoids having to re-arrange response
158-
* data on a recv that was shorter than expected */
159-
rspbuf = malloc(resp->data_len + sizeof(mic));
160-
if (!rspbuf)
161-
return -ENOMEM;
155+
resp_iov[1].iov_base = ((__u8 *)resp->data);
156+
resp_iov[1].iov_len = resp->data_len;
162157

163-
resp_iov[1].iov_base = rspbuf;
164-
resp_iov[1].iov_len = resp->data_len + sizeof(mic);
158+
resp_iov[2].iov_base = &mic;
159+
resp_iov[2].iov_len = sizeof(mic);
165160

166161
memset(&resp_msg, 0, sizeof(resp_msg));
167162
resp_msg.msg_name = &addr;
168163
resp_msg.msg_namelen = sizeof(addr);
169164
resp_msg.msg_iov = resp_iov;
170-
resp_msg.msg_iovlen = 2;
165+
resp_msg.msg_iovlen = 3;
171166

172167
len = ops.recvmsg(mctp->sd, &resp_msg, 0);
173168

174169
if (len < 0) {
175170
nvme_msg(ep->root, LOG_ERR,
176171
"Failure receiving MCTP message: %m\n");
177-
free(rspbuf);
178172
return len;
179173
}
180174

181-
if (len < resp->hdr_len + sizeof(mic) - 1) {
175+
if (len == 0) {
176+
nvme_msg(ep->root, LOG_WARNING, "No data from MCTP endpoint\n");
177+
return -1;
178+
}
179+
180+
/* Re-add the type byte, so we can work on aligned lengths from here */
181+
resp->hdr->type = MCTP_TYPE_NVME | MCTP_TYPE_MIC;
182+
len += 1;
183+
184+
/* The smallest response data is 8 bytes: generic 4-byte message header
185+
* plus four bytes of error data (excluding MIC). Ensure we have enough.
186+
*/
187+
if (len < 8 + sizeof(mic)) {
182188
nvme_msg(ep->root, LOG_ERR,
183189
"Invalid MCTP response: too short (%zd bytes, needed %zd)\n",
184-
len, resp->hdr_len + sizeof(mic) - 1);
185-
free(rspbuf);
190+
len, 8 + sizeof(mic));
186191
return -EIO;
187192
}
188-
resp->hdr->type = MCTP_TYPE_NVME | MCTP_TYPE_MIC;
189-
190-
len -= resp->hdr_len - 1;
191193

192-
memcpy(&mic, rspbuf + len - sizeof(mic), sizeof(mic));
193-
len -= sizeof(mic);
194+
/* We can't have header/payload data that isn't a multiple of 4 bytes */
195+
if (len & 0x3) {
196+
nvme_msg(ep->root, LOG_WARNING,
197+
"Response message has unaligned length (%zd)!\n",
198+
len);
199+
return -EIO;
200+
}
194201

195-
memcpy(resp->data, rspbuf, len);
196-
resp->data_len = len;
202+
/* If we have a shorter than expected response, we need to find the
203+
* MIC and the correct split between header & data. We know that the
204+
* split is 4-byte aligned, so the MIC will be entirely within one
205+
* of the iovecs.
206+
*/
207+
if (len == resp->hdr_len + resp->data_len + sizeof(mic)) {
208+
/* Common case: expected data length. Header, data and MIC
209+
* are already laid-out correctly. Nothing to do. */
210+
211+
} else if (len < resp->hdr_len + sizeof(mic)) {
212+
/* Response is smaller than the expected header. MIC is
213+
* somewhere in the header buf */
214+
resp->hdr_len = len - sizeof(mic);
215+
resp->data_len = 0;
216+
memcpy(&mic, ((uint8_t *)resp->hdr) + resp->hdr_len,
217+
sizeof(mic));
197218

198-
free(rspbuf);
219+
} else {
220+
/* We have a full header, but data is truncated - possibly
221+
* zero bytes. MIC is somewhere in the data buf */
222+
resp->data_len = len - resp->hdr_len - sizeof(mic);
223+
memcpy(&mic, ((uint8_t *)resp->data) + resp->data_len,
224+
sizeof(mic));
225+
}
199226

200227
resp->mic = le32_to_cpu(mic);
201228

test/mi-mctp.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,23 @@ static void test_rx_err(nvme_mi_ep_t ep, struct test_peer *peer)
172172
assert(rc != 0);
173173
}
174174

175+
static int tx_none(struct test_peer *peer, void *buf, size_t len)
176+
{
177+
return 0;
178+
}
179+
180+
static void test_tx_none(nvme_mi_ep_t ep, struct test_peer *peer)
181+
{
182+
struct nvme_mi_read_nvm_ss_info ss_info;
183+
int rc;
184+
185+
peer->tx_buf_len = 0;
186+
peer->rx_fn = tx_none;
187+
188+
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
189+
assert(rc != 0);
190+
}
191+
175192
static void test_tx_err(nvme_mi_ep_t ep, struct test_peer *peer)
176193
{
177194
struct nvme_mi_read_nvm_ss_info ss_info;
@@ -206,15 +223,102 @@ static void test_read_mi_data(nvme_mi_ep_t ep, struct test_peer *peer)
206223
assert(rc == 0);
207224
}
208225

226+
static void test_mi_resp_err(nvme_mi_ep_t ep, struct test_peer *peer)
227+
{
228+
struct nvme_mi_read_nvm_ss_info ss_info;
229+
int rc;
230+
231+
/* simple error response */
232+
peer->tx_buf[4] = 0x02; /* internal error */
233+
peer->tx_buf_len = 8;
234+
235+
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
236+
assert(rc == 0x2);
237+
}
238+
239+
static void test_admin_resp_err(nvme_mi_ep_t ep, struct test_peer *peer)
240+
{
241+
struct nvme_id_ctrl id;
242+
nvme_mi_ctrl_t ctrl;
243+
int rc;
244+
245+
ctrl = nvme_mi_init_ctrl(ep, 1);
246+
assert(ctrl);
247+
248+
/* Simple error response, will be shorter than the expected Admin
249+
* command response header. */
250+
peer->tx_buf[4] = 0x02; /* internal error */
251+
peer->tx_buf_len = 8;
252+
253+
rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
254+
assert(rc == 0x2);
255+
}
256+
257+
/* test: all 4-byte aligned response sizes - should be decoded into the
258+
* response status value. We use an admin command here as the header size will
259+
* be larger than the minimum header size (it contains the completion
260+
* doublewords), and we need to ensure that an error response is correctly
261+
* interpreted, including having the MIC extracted from the message.
262+
*/
263+
static void test_admin_resp_sizes(nvme_mi_ep_t ep, struct test_peer *peer)
264+
{
265+
struct nvme_id_ctrl id;
266+
nvme_mi_ctrl_t ctrl;
267+
unsigned int i;
268+
int rc;
269+
270+
ctrl = nvme_mi_init_ctrl(ep, 1);
271+
assert(ctrl);
272+
273+
peer->tx_buf[4] = 0x02; /* internal error */
274+
275+
for (i = 8; i <= 4096 + 8; i+=4) {
276+
peer->tx_buf_len = i;
277+
rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
278+
assert(rc == 2);
279+
}
280+
281+
nvme_mi_close_ctrl(ctrl);
282+
}
283+
284+
/* test: unaligned response sizes - should always report a transport error */
285+
static void test_admin_resp_sizes_unaligned(nvme_mi_ep_t ep, struct test_peer *peer)
286+
{
287+
struct nvme_id_ctrl id;
288+
nvme_mi_ctrl_t ctrl;
289+
unsigned int i;
290+
int rc;
291+
292+
ctrl = nvme_mi_init_ctrl(ep, 1);
293+
assert(ctrl);
294+
295+
peer->tx_buf[4] = 0x02; /* internal error */
296+
297+
for (i = 8; i <= 4096 + 8; i++) {
298+
peer->tx_buf_len = i;
299+
if (!(i & 0x3))
300+
continue;
301+
rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
302+
assert(rc < 0);
303+
}
304+
305+
nvme_mi_close_ctrl(ctrl);
306+
}
307+
209308
#define DEFINE_TEST(name) { #name, test_ ## name }
210309
struct test {
211310
const char *name;
212311
void (*fn)(nvme_mi_ep_t, struct test_peer *);
213312
} tests[] = {
214313
DEFINE_TEST(rx_err),
314+
DEFINE_TEST(tx_none),
215315
DEFINE_TEST(tx_err),
216316
DEFINE_TEST(tx_short),
217317
DEFINE_TEST(read_mi_data),
318+
DEFINE_TEST(mi_resp_err),
319+
DEFINE_TEST(admin_resp_err),
320+
DEFINE_TEST(admin_resp_sizes),
321+
DEFINE_TEST(admin_resp_sizes_unaligned),
218322
};
219323

220324
static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep,

0 commit comments

Comments
 (0)