Repository navigation
Conversation
| mask_sz - param_location); | ||
| if (mask_sz > param_location) | ||
| memcpy(tail_param, data + param_location, | ||
| mask_sz - param_location); |
There was a problem hiding this comment.
This is a case in which a user passed bad input,
the match_sz and match_criteria do not match - this will cause an underflow,
the problem is that there can be only one tail_param
and if it is dirty from a previous operation, you will pass it to
dr_ste_copy_mask_spec
causing an incorrect field copy.
I need to go over the code and see if we had other protections in this area that I am missing,
but the fix is partial.
There was a problem hiding this comment.
Confirmed and fixed. The shared buffer is now cleared before each partial copy, so the absent part of a section reads as zero rather than as the previous section's bytes. Verified over every section bit and every 4-byte mask_sz from 4 to 448: a short mask now yields a byte-identical result to the same mask zero-padded to the full header size.
c922995 to
902dfd8
Compare
|
You are right, and thanks for catching it -- the stale-buffer half is a real defect that my I reproduced it against the compiled function. With so the caller's MISC bytes were being copied into the INNER and MISC2 fields, exactly the It is worth noting why the two halves go together: on the unpatched code the underflow is The change now clears the shared buffer before each partial copy, so the absent part reads as
|
| uint64_t *mask_buf, size_t mask_sz, bool clear) | ||
| { | ||
| uint8_t *data = (uint8_t *)mask_buf; | ||
| /* A section whose bytes are only partly present in the caller's mask is |
There was a problem hiding this comment.
I think the code explains it, please drop.
| * parsed from this buffer, so the absent part has to read as zero. All | ||
| * sections share it, hence the clear before each partial copy. | ||
| */ | ||
| char tail_param[DEVX_ST_SZ_BYTES(dr_match_param)] = {}; |
There was a problem hiding this comment.
now we can drop the inital init "{}"
There was a problem hiding this comment.
Dropped as well -- every use of the buffer now clears it first, so the initialiser was redundant.
| DEVX_ST_SZ_BYTES(dr_match_set_misc2)) { | ||
| memcpy(tail_param, data + param_location, | ||
| mask_sz - param_location); | ||
| memset(tail_param, 0, sizeof(tail_param)); |
There was a problem hiding this comment.
what you did solved the bug.
I think an easier to read logic will be:
this way we don't copy unless we are in range, no point in running with empty buffer (as intended before)
/* Tmp comment for you only: don't do copy_mask if nothing to copy */
if ((match_criteria & DR_MATCHER_CRITERIA_INNER) &&
(mask_sz > param_location)) {
if (mask_sz >= param_location + DEVX_ST_SZ_BYTES(dr_match_spec)) {
/* Tmp comment for you only: after the range or exactly in the range - likely() */
buff = data + param_location;
} else {
/* Tmp comment for you only: in middle of the range */
memset(tail_param, 0, sizeof(tail_param));
memcpy(tail_param, data + param_location,
mask_sz - param_location);
buff = tail_param;
}
/* Tmp comment for you only: will happen only if needed */
dr_ste_copy_mask_spec(buff, &set_param->inner, clear);
}
There was a problem hiding this comment.
Sorry for the delay, and thanks for the nudge -- done now. I took your structure for all seven sections: a section is parsed only when the mask reaches into it, the direct pointer is used when the mask covers the whole section, and otherwise the scratch buffer is cleared and filled with what is present. Also dropped the comment and the "{}" initialiser as you asked. Details in the review replies.
dr_ste_copy_param() parses the caller's match mask one section at a time.
A section that the mask does not reach was parsed from a single shared
scratch buffer:
char tail_param[DEVX_ST_SZ_BYTES(dr_match_param)] = {};
...
if (match_criteria & DR_MATCHER_CRITERIA_MISC) {
if (mask_sz < param_location +
DEVX_ST_SZ_BYTES(dr_match_set_misc)) {
memcpy(tail_param, data + param_location,
mask_sz - param_location);
buff = tail_param;
} else {
buff = data + param_location;
}
dr_ste_copy_mask_misc(buff, &set_param->misc, clear);
}
Two defects follow from that.
First, the copy length is computed without establishing that the mask
reaches the section. When mask_sz is smaller than param_location the
subtraction is done in size_t and underflows, so memcpy() is asked for
about SIZE_MAX bytes: it reads far past the caller's buffer and writes
past the 448 byte scratch buffer. The rule path checks the mask for an
upper bound and alignment only (dr_rule.c), never for a lower one.
Second, the buffer is shared by all seven sections and was initialised
only once at function entry. A section whose mask_sz falls inside it
copies the bytes that are present and leaves the remainder holding what
the previous section left behind. Those bytes are then read as if the
caller had supplied them, so fields the caller never specified take
values from a different section of its own mask. The initialiser shows
the intent is for the absent part to read as zero.
A section is now parsed only when the mask reaches into it, and the
scratch buffer is cleared before each partial copy, so the absent part
reads as zero:
if ((match_criteria & DR_MATCHER_CRITERIA_MISC) &&
(mask_sz > param_location)) {
if (mask_sz >= param_location +
DEVX_ST_SZ_BYTES(dr_match_set_misc)) {
buff = data + param_location;
} else {
memset(tail_param, 0, sizeof(tail_param));
memcpy(tail_param, data + param_location,
mask_sz - param_location);
buff = tail_param;
}
dr_ste_copy_mask_misc(buff, &set_param->misc, clear);
}
A mask shorter than the match header then behaves exactly as the same
mask zero padded to DEVX_ST_SZ_BYTES(dr_match_param). set_param is
zeroed by the caller that can reach the partial path, and the other
caller, dr_matcher_copy_mask(), always passes a full size buffer, so it
takes the direct path and is unaffected.
Fixes: 7ec30b8 ("mlx5: Add Steering entry (STE) utilities")
Signed-off-by: Li Bingzhuo <lbz2770828522@gmail.com>
902dfd8 to
449134c
Compare
|
Both points applied, and I took the structure you sketched for all seven sections rather than only The comment and the One thing I checked before dropping the initialiser: skipping a section means Verification, both against the compiled object:
|
dr_ste_copy_param()guards itsmemcpy()with an upper bound only. Sixregions are parsed, and each one computes
mask_sz - param_locationwithoutestablishing that
mask_szreachesparam_location. When the caller's mask isshorter than the fixed match header the subtraction wraps, and
memcpy()isasked for that many bytes into a 448 byte stack buffer. The rule that supplies
the mask is checked for an upper bound and alignment in
dr_rule.c, never for alower one.
A mask that does not reach the region being parsed has no bytes to copy, so the
change guards the copy on there being something to copy rather than clamping the
length afterwards. All six sites are covered, and the change is local to
providers/mlx5/dr_ste.c.Reproduction without mlx5 hardware: call
dr_ste_copy_param()with a mask oneblock shorter than
param_location. AddressSanitizer reportsnegative-size-param, and a build with_FORTIFY_SOURCEaborts in the sameplace.