Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 1 | From 13d0b106e6512dd984bbec6e8b450731989d0618 Mon Sep 17 00:00:00 2001 |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 2 | From: Alex Schendel <alex.schendel@intel.com> |
| 3 | Date: Fri, 9 Jun 2023 16:35:24 -0700 |
| 4 | Subject: [PATCH] Fru: Fix edit field not checking area existence |
| 5 | |
| 6 | The current implementation of ipmitool fru edit does not perform proper |
| 7 | checks when attempting to resize the FRU. This results in undesireable |
| 8 | changes to the FRU in several instances: |
| 9 | 1. If the FRU is shrinking and a FRU area does not exist (offset 0), |
| 10 | ipmitool may attempt to shift it forwards (decrementing the offset). |
| 11 | This results in a wraparound to 0xFF, leaving an erroneous field offset. |
| 12 | 2. If the areas are not in the exact order given as an example in the |
| 13 | FRU spec, ipmitool may shift the wrong fields, which would cause data |
| 14 | loss. (the FRU spec does not specify a required order for FRU fields) |
| 15 | 3. If the FRU is being enlarged after a fru field edit, the FRU size is |
| 16 | not properly modified before writing the FRU, so the end of the FRU |
| 17 | becomes truncated, resulting in data loss. |
| 18 | |
| 19 | This commit addresses these three issues by: |
| 20 | 1. Confirming that a area's does not have an offset of 0x00 before |
| 21 | attempting to shift it. |
| 22 | 2. Ensuring that the area's offset is after the area that was modified |
| 23 | before attempting to shift it. |
| 24 | 3. Properly edit the size of the FRU before the FRU is written. |
| 25 | |
| 26 | Tested: |
| 27 | Shrinking a FRU was tested with and without the change: |
| 28 | New Header without change: |
| 29 | 01 00 00 01 0a ff 00 f5 |
| 30 | ^^ |
| 31 | Note that the Multi Record area now has an offset of 0xFF. |
| 32 | |
| 33 | New Header with change: |
| 34 | 01 00 00 01 0a 00 00 f4 |
| 35 | ^^ |
| 36 | Note that the Multi Record area retains its offset of 0x00. |
| 37 | |
| 38 | This change also includes printouts specifying what offsets are found |
| 39 | and when they are being shifted, as well as data being erased if the FRU |
| 40 | is being shrunk: |
| 41 | Offset: 0 |
| 42 | Offset: 0 |
| 43 | Offset: 8 |
| 44 | Offset: 88 moving by -8 bytes. |
| 45 | Offset: 0 |
| 46 | Erasing leftover data from 200 to 208 |
| 47 | |
| 48 | After shrinking the FRU, the FRU was reverted to its original state with |
| 49 | the fix in place: |
| 50 | 01 00 00 01 0b 00 00 f3 |
| 51 | ^^ |
| 52 | This resulted in only the product area offset being updated as expected. |
| 53 | Offset: 0 |
| 54 | Offset: 0 |
| 55 | Offset: 8 |
| 56 | Offset: 80 moving by 8 bytes. |
| 57 | Offset: 0 |
| 58 | |
| 59 | The implementation of IPMI FRU write used in these tests errors out |
| 60 | without writing the FRU if a checksum fails to pass, so without this |
| 61 | fix, it was impossible to enlarge the FRU. This is because without the |
| 62 | change, the last 8 bytes of the FRU would be truncated which would |
| 63 | result in the checksum of the final FRU area being lost which would thus |
| 64 | trigger this FRU write failure. |
| 65 | |
| 66 | Signed-off-by: Alex Schendel <alex.schendel@intel.com> |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 67 | Signed-off-by: Zhikui Ren <zhikui.ren@intel.com> |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 68 | --- |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 69 | include/ipmitool/ipmi_fru.h | 1 + |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 70 | lib/ipmi_fru.c | 170 +++++++++++++++++++++++------------- |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 71 | 2 files changed, 110 insertions(+), 61 deletions(-) |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 72 | |
| 73 | diff --git a/include/ipmitool/ipmi_fru.h b/include/ipmitool/ipmi_fru.h |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 74 | index c6b3a54..13f2958 100644 |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 75 | --- a/include/ipmitool/ipmi_fru.h |
| 76 | +++ b/include/ipmitool/ipmi_fru.h |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 77 | @@ -46,6 +46,7 @@ |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 78 | #define GET_FRU_INFO 0x10 |
| 79 | #define GET_FRU_DATA 0x11 |
| 80 | #define SET_FRU_DATA 0x12 |
| 81 | +#define FRU_AREA_COUNT 5 |
| 82 | |
| 83 | enum { |
| 84 | FRU_CHASSIS_PARTNO, |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 85 | diff --git a/lib/ipmi_fru.c b/lib/ipmi_fru.c |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 86 | index 4d1dbbb..effc1a8 100644 |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 87 | --- a/lib/ipmi_fru.c |
| 88 | +++ b/lib/ipmi_fru.c |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 89 | @@ -131,7 +131,8 @@ static int ipmi_fru_set_field_string(struct ipmi_intf * intf, unsigned |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 90 | static int |
| 91 | ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
| 92 | struct fru_info fru, struct fru_header header, |
| 93 | - uint8_t f_type, uint8_t f_index, char *f_string); |
| 94 | + uint8_t f_type, uint8_t f_index, char *f_string, |
| 95 | + int new_size); |
| 96 | |
| 97 | static void |
| 98 | fru_area_print_multirec_bloc(struct ipmi_intf * intf, struct fru_info * fru, |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 99 | @@ -4997,12 +4998,19 @@ f_type, uint8_t f_index, char *f_string) |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 100 | rc = -1; |
| 101 | goto ipmi_fru_set_field_string_out; |
| 102 | } |
| 103 | - } |
| 104 | - else { |
| 105 | - printf("String size are not equal, resizing fru to fit new string\n"); |
| 106 | - if( |
| 107 | - ipmi_fru_set_field_string_rebuild(intf,fruId,fru,header,f_type,f_index,f_string) |
| 108 | - ) |
| 109 | + } |
| 110 | + else { |
| 111 | + int new_fru_size = fru.size; |
| 112 | + int change = |
| 113 | + strlen((const char *)f_string) - strlen((const char *)fru_area); |
| 114 | + if (change > 0) |
| 115 | + { |
| 116 | + /* Fru record is padded to be 8 bytes aligned */ |
| 117 | + new_fru_size = fru.size + change + 8; |
| 118 | + } |
| 119 | + if (ipmi_fru_set_field_string_rebuild(intf, fruId, fru, header, |
| 120 | + f_type, f_index, f_string, |
| 121 | + new_fru_size)) |
| 122 | { |
| 123 | rc = -1; |
| 124 | goto ipmi_fru_set_field_string_out; |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 125 | @@ -5048,7 +5056,8 @@ ipmi_fru_set_field_string_out: |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 126 | static int |
| 127 | ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
| 128 | struct fru_info fru, struct fru_header header, |
| 129 | - uint8_t f_type, uint8_t f_index, char *f_string) |
| 130 | + uint8_t f_type, uint8_t f_index, char *f_string, |
| 131 | + int new_size) |
| 132 | { |
| 133 | int i = 0; |
| 134 | uint8_t *fru_data_old = NULL; |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 135 | @@ -5065,7 +5074,7 @@ ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 136 | |
| 137 | fru_data_old = calloc( fru.size, sizeof(uint8_t) ); |
| 138 | |
| 139 | - fru_data_new = malloc( fru.size ); |
| 140 | + fru_data_new = malloc( new_size ); |
| 141 | |
| 142 | if (!fru_data_old || !fru_data_new) { |
| 143 | printf("Out of memory!\n"); |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 144 | @@ -5205,43 +5214,91 @@ ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 145 | #endif |
| 146 | |
| 147 | /* Must move sections */ |
| 148 | - /* Section that can be modified are as follow |
| 149 | - Chassis |
| 150 | - Board |
| 151 | - product */ |
| 152 | - |
| 153 | - /* Chassis type field */ |
| 154 | - if (f_type == 'c' ) |
| 155 | + /* IPMI FRU Spec does not specify the order of areas in the FRU. |
| 156 | + * Therefore, we must check each section's current offset in order to determine |
| 157 | + * which areas much be adjusted. |
| 158 | + */ |
| 159 | + |
| 160 | + /* The Internal Use Area does not require the area length be provided, so we must |
| 161 | + * work to calculate the length. |
| 162 | + */ |
| 163 | + bool internal_move = false; |
| 164 | + uint8_t nearest_area = fru.size; |
| 165 | + uint8_t last_area = 0x00; |
| 166 | + uint32_t end_of_fru; |
| 167 | + if (header.offset.internal != 0 && header.offset.internal > header_offset) |
| 168 | { |
| 169 | - printf("Moving Section Chassis, from %i to %i\n", |
| 170 | - ((header.offset.board) * 8), |
| 171 | - ((header.offset.board + change_size_by_8) * 8) |
| 172 | - ); |
| 173 | - memcpy( |
| 174 | - (fru_data_new + ((header.offset.board + change_size_by_8) * 8)), |
| 175 | - (fru_data_old + (header.offset.board) * 8), |
| 176 | - board_len |
| 177 | - ); |
| 178 | - header.offset.board += change_size_by_8; |
| 179 | + internal_move = true; |
| 180 | } |
| 181 | - /* Board type field */ |
| 182 | - if ((f_type == 'c' ) || (f_type == 'b' )) |
| 183 | + /* Check Chassis, Board, Product, and Multirecord Area offsets to see if they need |
| 184 | + * to be moved. |
| 185 | + */ |
| 186 | + for (int i = 0; i < FRU_AREA_COUNT; i++) |
| 187 | { |
| 188 | - printf("Moving Section Product, from %i to %i\n", |
| 189 | - ((header.offset.product) * 8), |
| 190 | - ((header.offset.product + change_size_by_8) * 8) |
| 191 | - ); |
| 192 | - memcpy( |
| 193 | - (fru_data_new + ((header.offset.product + change_size_by_8) * 8)), |
| 194 | - (fru_data_old + (header.offset.product) * 8), |
| 195 | - product_len |
| 196 | - ); |
| 197 | - header.offset.product += change_size_by_8; |
| 198 | + #ifdef DBG_RESIZE_FRU |
| 199 | + printf("Offset: %i", header.offsets[i] * 8); |
| 200 | + #endif |
| 201 | + /* Offset of zero means area does not exist. |
| 202 | + * Internal Use Area must be handled separately |
| 203 | + */ |
| 204 | + if (header.offsets[i] <= 0 || header.offsets[i] == header.offset.internal) |
| 205 | + { |
| 206 | +#ifdef DBG_RESIZE_FRU |
| 207 | + printf("\n"); |
| 208 | +#endif |
| 209 | + continue; |
| 210 | + } |
| 211 | + /* Internal Use Area length will be calculated by finding the closest area |
| 212 | + * following it. |
| 213 | + */ |
| 214 | + if (internal_move && header.offsets[i] > header.offset.internal && |
| 215 | + header.offsets[i] < nearest_area) |
| 216 | + { |
| 217 | + nearest_area = header.offsets[i]; |
| 218 | + } |
| 219 | + if (last_area < header.offsets[i]) |
| 220 | + { |
| 221 | + last_area = header.offsets[i]; |
| 222 | + end_of_fru = (header.offsets[i] + *(fru_data_old + (header.offsets[i] * 8) + 1)) * 8; |
| 223 | + if (header.offsets[i] == header.offset.multi) |
| 224 | + { |
| 225 | + end_of_fru = (header.offsets[i] + *(fru_data_old + (header.offsets[i] * 8) + 1)) * 8; |
| 226 | + } |
| 227 | + } |
| 228 | + if ((header.offsets[i] * 8) > header_offset) |
| 229 | + { |
| 230 | +#ifdef DBG_RESIZE_FRU |
| 231 | + printf(" moving by %i bytes.", change_size_by_8 * 8); |
| 232 | +#endif |
| 233 | + uint32_t length = *(fru_data_old + (header.offsets[i] * 8) + 1) * 8; |
| 234 | + /* MultiRecord Area length is third byte rather than second. */ |
| 235 | + if(header.offsets[i] == header.offset.multi) |
| 236 | + { |
| 237 | + length = *(fru_data_old + (header.offsets[i] * 8) + 2) * 8; |
| 238 | + } |
| 239 | + memcpy( |
| 240 | + (fru_data_new + ((header.offsets[i] + change_size_by_8) * 8)), |
| 241 | + (fru_data_old + (header.offsets[i]) * 8), |
| 242 | + length |
| 243 | + ); |
| 244 | + header.offsets[i] += change_size_by_8; |
| 245 | + } |
| 246 | +#ifdef DBG_RESIZE_FRU |
| 247 | + printf("\n"); |
| 248 | +#endif |
| 249 | } |
| 250 | - |
| 251 | - if ((f_type == 'c' ) || (f_type == 'b' ) || (f_type == 'p' )) { |
| 252 | - printf("Change multi offset from %d to %d\n", header.offset.multi, header.offset.multi + change_size_by_8); |
| 253 | - header.offset.multi += change_size_by_8; |
| 254 | + if (internal_move) |
| 255 | + { |
| 256 | + /* If the internal area is the final area in the FRU, then the only bearing |
| 257 | + * we have for the length of the FRU is the size of the FRU. |
| 258 | + */ |
| 259 | + uint32_t length = nearest_area - header.offset.internal; |
| 260 | + memcpy( |
| 261 | + (fru_data_new + ((header.offset.internal + change_size_by_8) * 8)), |
| 262 | + (fru_data_old + (header.offset.internal) * 8), |
| 263 | + length |
| 264 | + ); |
| 265 | + header.offset.internal += change_size_by_8; |
| 266 | } |
| 267 | |
| 268 | /* Adjust length of the section */ |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 269 | @@ -5271,27 +5328,18 @@ ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 270 | memcpy(fru_data_new, pfru_header, sizeof(struct fru_header)); |
| 271 | } |
| 272 | |
| 273 | - /* Move remaining sections in 1 copy */ |
| 274 | - printf("Moving Remaining Bytes (Multi-Rec , etc..), from %i to %i\n", |
| 275 | - remaining_offset, |
| 276 | - ((header.offset.product) * 8) + product_len_new |
| 277 | - ); |
| 278 | - if(((header.offset.product * 8) + product_len_new - remaining_offset) < 0) |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 279 | + /* If FRU has shrunk in size, zero-out any leftover data */ |
| 280 | + if (change_size_by_8 < 0) |
| 281 | { |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 282 | - memcpy( |
| 283 | - fru_data_new + (header.offset.product * 8) + product_len_new, |
| 284 | - fru_data_old + remaining_offset, |
| 285 | - fru.size - remaining_offset |
| 286 | - ); |
| 287 | - } |
| 288 | - else |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 289 | - { |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 290 | - memcpy( |
| 291 | - fru_data_new + (header.offset.product * 8) + product_len_new, |
| 292 | - fru_data_old + remaining_offset, |
| 293 | - fru.size - ((header.offset.product * 8) + product_len_new) |
| 294 | - ); |
| 295 | + end_of_fru += change_size_by_8 * 8; |
| 296 | + int length_of_erase = change_size_by_8 * -1 * 8; |
| 297 | +#ifdef DBG_RESIZE_FRU |
| 298 | + printf("Erasing leftover data from %i to %i\n", end_of_fru, end_of_fru + length_of_erase); |
| 299 | +#endif |
| 300 | + memset(fru_data_new + end_of_fru, 0, length_of_erase); |
| 301 | } |
| 302 | + /* Step 7 assumes fru.size is the size of the new FRU. */ |
| 303 | + fru.size += (change_size_by_8 * 8); |
| 304 | } |
| 305 | |
| 306 | /* Update only if it's fits padding length as defined in the spec, otherwise, it's an internal |
| 307 | -- |
Johnathan Mantey | 5969d16 | 2023-10-26 14:31:24 -0700 | [diff] [blame] | 308 | 2.41.0 |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 309 | |