Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 1 | From 255b93dfe8adcbf286c94706906aa8fdc60d388b 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 | --- |
| 69 | include/ipmitool/ipmi_fru.h | 3 +- |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 70 | lib/ipmi_fru.c | 170 +++++++++++++++++++++++------------- |
| 71 | 2 files changed, 111 insertions(+), 62 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 |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 74 | index b17870c..549915d 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 |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 77 | @@ -47,6 +47,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, |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 85 | @@ -83,7 +84,7 @@ struct fru_header { |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 86 | uint8_t product; |
| 87 | uint8_t multi; |
| 88 | } offset; |
| 89 | - uint8_t offsets[5]; |
| 90 | + uint8_t offsets[FRU_AREA_COUNT]; |
| 91 | }; |
| 92 | uint8_t pad; |
| 93 | uint8_t checksum; |
| 94 | diff --git a/lib/ipmi_fru.c b/lib/ipmi_fru.c |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 95 | index 4a5018d..f776642 100644 |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 96 | --- a/lib/ipmi_fru.c |
| 97 | +++ b/lib/ipmi_fru.c |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 98 | @@ -130,7 +130,8 @@ static int ipmi_fru_set_field_string(struct ipmi_intf * intf, unsigned |
| 99 | static int |
| 100 | ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
| 101 | struct fru_info fru, struct fru_header header, |
| 102 | - uint8_t f_type, uint8_t f_index, char *f_string); |
| 103 | + uint8_t f_type, uint8_t f_index, char *f_string, |
| 104 | + int new_size); |
| 105 | |
| 106 | static void |
| 107 | fru_area_print_multirec_bloc(struct ipmi_intf * intf, struct fru_info * fru, |
| 108 | @@ -4835,12 +4836,19 @@ f_type, uint8_t f_index, char *f_string) |
| 109 | rc = -1; |
| 110 | goto ipmi_fru_set_field_string_out; |
| 111 | } |
| 112 | - } |
| 113 | - else { |
| 114 | - printf("String size are not equal, resizing fru to fit new string\n"); |
| 115 | - if( |
| 116 | - ipmi_fru_set_field_string_rebuild(intf,fruId,fru,header,f_type,f_index,f_string) |
| 117 | - ) |
| 118 | + } |
| 119 | + else { |
| 120 | + int new_fru_size = fru.size; |
| 121 | + int change = |
| 122 | + strlen((const char *)f_string) - strlen((const char *)fru_area); |
| 123 | + if (change > 0) |
| 124 | + { |
| 125 | + /* Fru record is padded to be 8 bytes aligned */ |
| 126 | + new_fru_size = fru.size + change + 8; |
| 127 | + } |
| 128 | + if (ipmi_fru_set_field_string_rebuild(intf, fruId, fru, header, |
| 129 | + f_type, f_index, f_string, |
| 130 | + new_fru_size)) |
| 131 | { |
| 132 | rc = -1; |
| 133 | goto ipmi_fru_set_field_string_out; |
| 134 | @@ -4886,7 +4894,8 @@ ipmi_fru_set_field_string_out: |
| 135 | static int |
| 136 | ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
| 137 | struct fru_info fru, struct fru_header header, |
| 138 | - uint8_t f_type, uint8_t f_index, char *f_string) |
| 139 | + uint8_t f_type, uint8_t f_index, char *f_string, |
| 140 | + int new_size) |
| 141 | { |
| 142 | int i = 0; |
| 143 | uint8_t *fru_data_old = NULL; |
| 144 | @@ -4903,7 +4912,7 @@ ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
| 145 | |
| 146 | fru_data_old = calloc( fru.size, sizeof(uint8_t) ); |
| 147 | |
| 148 | - fru_data_new = malloc( fru.size ); |
| 149 | + fru_data_new = malloc( new_size ); |
| 150 | |
| 151 | if (!fru_data_old || !fru_data_new) { |
| 152 | printf("Out of memory!\n"); |
| 153 | @@ -5043,43 +5052,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] | 154 | #endif |
| 155 | |
| 156 | /* Must move sections */ |
| 157 | - /* Section that can be modified are as follow |
| 158 | - Chassis |
| 159 | - Board |
| 160 | - product */ |
| 161 | - |
| 162 | - /* Chassis type field */ |
| 163 | - if (f_type == 'c' ) |
| 164 | + /* IPMI FRU Spec does not specify the order of areas in the FRU. |
| 165 | + * Therefore, we must check each section's current offset in order to determine |
| 166 | + * which areas much be adjusted. |
| 167 | + */ |
| 168 | + |
| 169 | + /* The Internal Use Area does not require the area length be provided, so we must |
| 170 | + * work to calculate the length. |
| 171 | + */ |
| 172 | + bool internal_move = false; |
| 173 | + uint8_t nearest_area = fru.size; |
| 174 | + uint8_t last_area = 0x00; |
| 175 | + uint32_t end_of_fru; |
| 176 | + if (header.offset.internal != 0 && header.offset.internal > header_offset) |
| 177 | { |
| 178 | - printf("Moving Section Chassis, from %i to %i\n", |
| 179 | - ((header.offset.board) * 8), |
| 180 | - ((header.offset.board + change_size_by_8) * 8) |
| 181 | - ); |
| 182 | - memcpy( |
| 183 | - (fru_data_new + ((header.offset.board + change_size_by_8) * 8)), |
| 184 | - (fru_data_old + (header.offset.board) * 8), |
| 185 | - board_len |
| 186 | - ); |
| 187 | - header.offset.board += change_size_by_8; |
| 188 | + internal_move = true; |
| 189 | } |
| 190 | - /* Board type field */ |
| 191 | - if ((f_type == 'c' ) || (f_type == 'b' )) |
| 192 | + /* Check Chassis, Board, Product, and Multirecord Area offsets to see if they need |
| 193 | + * to be moved. |
| 194 | + */ |
| 195 | + for (int i = 0; i < FRU_AREA_COUNT; i++) |
| 196 | { |
| 197 | - printf("Moving Section Product, from %i to %i\n", |
| 198 | - ((header.offset.product) * 8), |
| 199 | - ((header.offset.product + change_size_by_8) * 8) |
| 200 | - ); |
| 201 | - memcpy( |
| 202 | - (fru_data_new + ((header.offset.product + change_size_by_8) * 8)), |
| 203 | - (fru_data_old + (header.offset.product) * 8), |
| 204 | - product_len |
| 205 | - ); |
| 206 | - header.offset.product += change_size_by_8; |
| 207 | + #ifdef DBG_RESIZE_FRU |
| 208 | + printf("Offset: %i", header.offsets[i] * 8); |
| 209 | + #endif |
| 210 | + /* Offset of zero means area does not exist. |
| 211 | + * Internal Use Area must be handled separately |
| 212 | + */ |
| 213 | + if (header.offsets[i] <= 0 || header.offsets[i] == header.offset.internal) |
| 214 | + { |
| 215 | +#ifdef DBG_RESIZE_FRU |
| 216 | + printf("\n"); |
| 217 | +#endif |
| 218 | + continue; |
| 219 | + } |
| 220 | + /* Internal Use Area length will be calculated by finding the closest area |
| 221 | + * following it. |
| 222 | + */ |
| 223 | + if (internal_move && header.offsets[i] > header.offset.internal && |
| 224 | + header.offsets[i] < nearest_area) |
| 225 | + { |
| 226 | + nearest_area = header.offsets[i]; |
| 227 | + } |
| 228 | + if (last_area < header.offsets[i]) |
| 229 | + { |
| 230 | + last_area = header.offsets[i]; |
| 231 | + end_of_fru = (header.offsets[i] + *(fru_data_old + (header.offsets[i] * 8) + 1)) * 8; |
| 232 | + if (header.offsets[i] == header.offset.multi) |
| 233 | + { |
| 234 | + end_of_fru = (header.offsets[i] + *(fru_data_old + (header.offsets[i] * 8) + 1)) * 8; |
| 235 | + } |
| 236 | + } |
| 237 | + if ((header.offsets[i] * 8) > header_offset) |
| 238 | + { |
| 239 | +#ifdef DBG_RESIZE_FRU |
| 240 | + printf(" moving by %i bytes.", change_size_by_8 * 8); |
| 241 | +#endif |
| 242 | + uint32_t length = *(fru_data_old + (header.offsets[i] * 8) + 1) * 8; |
| 243 | + /* MultiRecord Area length is third byte rather than second. */ |
| 244 | + if(header.offsets[i] == header.offset.multi) |
| 245 | + { |
| 246 | + length = *(fru_data_old + (header.offsets[i] * 8) + 2) * 8; |
| 247 | + } |
| 248 | + memcpy( |
| 249 | + (fru_data_new + ((header.offsets[i] + change_size_by_8) * 8)), |
| 250 | + (fru_data_old + (header.offsets[i]) * 8), |
| 251 | + length |
| 252 | + ); |
| 253 | + header.offsets[i] += change_size_by_8; |
| 254 | + } |
| 255 | +#ifdef DBG_RESIZE_FRU |
| 256 | + printf("\n"); |
| 257 | +#endif |
| 258 | } |
| 259 | - |
| 260 | - if ((f_type == 'c' ) || (f_type == 'b' ) || (f_type == 'p' )) { |
| 261 | - printf("Change multi offset from %d to %d\n", header.offset.multi, header.offset.multi + change_size_by_8); |
| 262 | - header.offset.multi += change_size_by_8; |
| 263 | + if (internal_move) |
| 264 | + { |
| 265 | + /* If the internal area is the final area in the FRU, then the only bearing |
| 266 | + * we have for the length of the FRU is the size of the FRU. |
| 267 | + */ |
| 268 | + uint32_t length = nearest_area - header.offset.internal; |
| 269 | + memcpy( |
| 270 | + (fru_data_new + ((header.offset.internal + change_size_by_8) * 8)), |
| 271 | + (fru_data_old + (header.offset.internal) * 8), |
| 272 | + length |
| 273 | + ); |
| 274 | + header.offset.internal += change_size_by_8; |
| 275 | } |
| 276 | |
| 277 | /* Adjust length of the section */ |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 278 | @@ -5109,27 +5166,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] | 279 | memcpy(fru_data_new, pfru_header, sizeof(struct fru_header)); |
| 280 | } |
| 281 | |
| 282 | - /* Move remaining sections in 1 copy */ |
| 283 | - printf("Moving Remaining Bytes (Multi-Rec , etc..), from %i to %i\n", |
| 284 | - remaining_offset, |
| 285 | - ((header.offset.product) * 8) + product_len_new |
| 286 | - ); |
| 287 | - if(((header.offset.product * 8) + product_len_new - remaining_offset) < 0) |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 288 | + /* If FRU has shrunk in size, zero-out any leftover data */ |
| 289 | + if (change_size_by_8 < 0) |
| 290 | { |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 291 | - memcpy( |
| 292 | - fru_data_new + (header.offset.product * 8) + product_len_new, |
| 293 | - fru_data_old + remaining_offset, |
| 294 | - fru.size - remaining_offset |
| 295 | - ); |
| 296 | - } |
| 297 | - else |
Zhikui Ren | a47c5b7 | 2023-09-27 14:32:44 -0700 | [diff] [blame] | 298 | - { |
Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 299 | - memcpy( |
| 300 | - fru_data_new + (header.offset.product * 8) + product_len_new, |
| 301 | - fru_data_old + remaining_offset, |
| 302 | - fru.size - ((header.offset.product * 8) + product_len_new) |
| 303 | - ); |
| 304 | + end_of_fru += change_size_by_8 * 8; |
| 305 | + int length_of_erase = change_size_by_8 * -1 * 8; |
| 306 | +#ifdef DBG_RESIZE_FRU |
| 307 | + printf("Erasing leftover data from %i to %i\n", end_of_fru, end_of_fru + length_of_erase); |
| 308 | +#endif |
| 309 | + memset(fru_data_new + end_of_fru, 0, length_of_erase); |
| 310 | } |
| 311 | + /* Step 7 assumes fru.size is the size of the new FRU. */ |
| 312 | + fru.size += (change_size_by_8 * 8); |
| 313 | } |
| 314 | |
| 315 | /* Update only if it's fits padding length as defined in the spec, otherwise, it's an internal |
| 316 | -- |
| 317 | 2.25.1 |
| 318 | |