Alex Schendel | 6a69fe4 | 2023-06-20 19:42:53 -0700 | [diff] [blame] | 1 | From 066f647fbec1f3177d673d157b18ff0c0f517627 Mon Sep 17 00:00:00 2001 |
| 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> |
| 67 | --- |
| 68 | include/ipmitool/ipmi_fru.h | 3 +- |
| 69 | lib/ipmi_fru.c | 143 +++++++++++++++++++++++------------- |
| 70 | 2 files changed, 93 insertions(+), 53 deletions(-) |
| 71 | |
| 72 | diff --git a/include/ipmitool/ipmi_fru.h b/include/ipmitool/ipmi_fru.h |
| 73 | index 4d4d6c6..e7e8876 100644 |
| 74 | --- a/include/ipmitool/ipmi_fru.h |
| 75 | +++ b/include/ipmitool/ipmi_fru.h |
| 76 | @@ -46,6 +46,7 @@ |
| 77 | #define GET_FRU_INFO 0x10 |
| 78 | #define GET_FRU_DATA 0x11 |
| 79 | #define SET_FRU_DATA 0x12 |
| 80 | +#define FRU_AREA_COUNT 5 |
| 81 | |
| 82 | enum { |
| 83 | FRU_CHASSIS_PARTNO, |
| 84 | @@ -82,7 +83,7 @@ struct fru_header { |
| 85 | uint8_t product; |
| 86 | uint8_t multi; |
| 87 | } offset; |
| 88 | - uint8_t offsets[5]; |
| 89 | + uint8_t offsets[FRU_AREA_COUNT]; |
| 90 | }; |
| 91 | uint8_t pad; |
| 92 | uint8_t checksum; |
| 93 | diff --git a/lib/ipmi_fru.c b/lib/ipmi_fru.c |
| 94 | index 3d1d8a1..2687635 100644 |
| 95 | --- a/lib/ipmi_fru.c |
| 96 | +++ b/lib/ipmi_fru.c |
| 97 | @@ -5044,43 +5044,91 @@ ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
| 98 | #endif |
| 99 | |
| 100 | /* Must move sections */ |
| 101 | - /* Section that can be modified are as follow |
| 102 | - Chassis |
| 103 | - Board |
| 104 | - product */ |
| 105 | - |
| 106 | - /* Chassis type field */ |
| 107 | - if (f_type == 'c' ) |
| 108 | + /* IPMI FRU Spec does not specify the order of areas in the FRU. |
| 109 | + * Therefore, we must check each section's current offset in order to determine |
| 110 | + * which areas much be adjusted. |
| 111 | + */ |
| 112 | + |
| 113 | + /* The Internal Use Area does not require the area length be provided, so we must |
| 114 | + * work to calculate the length. |
| 115 | + */ |
| 116 | + bool internal_move = false; |
| 117 | + uint8_t nearest_area = fru.size; |
| 118 | + uint8_t last_area = 0x00; |
| 119 | + uint32_t end_of_fru; |
| 120 | + if (header.offset.internal != 0 && header.offset.internal > header_offset) |
| 121 | { |
| 122 | - printf("Moving Section Chassis, from %i to %i\n", |
| 123 | - ((header.offset.board) * 8), |
| 124 | - ((header.offset.board + change_size_by_8) * 8) |
| 125 | - ); |
| 126 | - memcpy( |
| 127 | - (fru_data_new + ((header.offset.board + change_size_by_8) * 8)), |
| 128 | - (fru_data_old + (header.offset.board) * 8), |
| 129 | - board_len |
| 130 | - ); |
| 131 | - header.offset.board += change_size_by_8; |
| 132 | + internal_move = true; |
| 133 | } |
| 134 | - /* Board type field */ |
| 135 | - if ((f_type == 'c' ) || (f_type == 'b' )) |
| 136 | + /* Check Chassis, Board, Product, and Multirecord Area offsets to see if they need |
| 137 | + * to be moved. |
| 138 | + */ |
| 139 | + for (int i = 0; i < FRU_AREA_COUNT; i++) |
| 140 | { |
| 141 | - printf("Moving Section Product, from %i to %i\n", |
| 142 | - ((header.offset.product) * 8), |
| 143 | - ((header.offset.product + change_size_by_8) * 8) |
| 144 | - ); |
| 145 | - memcpy( |
| 146 | - (fru_data_new + ((header.offset.product + change_size_by_8) * 8)), |
| 147 | - (fru_data_old + (header.offset.product) * 8), |
| 148 | - product_len |
| 149 | - ); |
| 150 | - header.offset.product += change_size_by_8; |
| 151 | + #ifdef DBG_RESIZE_FRU |
| 152 | + printf("Offset: %i", header.offsets[i] * 8); |
| 153 | + #endif |
| 154 | + /* Offset of zero means area does not exist. |
| 155 | + * Internal Use Area must be handled separately |
| 156 | + */ |
| 157 | + if (header.offsets[i] <= 0 || header.offsets[i] == header.offset.internal) |
| 158 | + { |
| 159 | +#ifdef DBG_RESIZE_FRU |
| 160 | + printf("\n"); |
| 161 | +#endif |
| 162 | + continue; |
| 163 | + } |
| 164 | + /* Internal Use Area length will be calculated by finding the closest area |
| 165 | + * following it. |
| 166 | + */ |
| 167 | + if (internal_move && header.offsets[i] > header.offset.internal && |
| 168 | + header.offsets[i] < nearest_area) |
| 169 | + { |
| 170 | + nearest_area = header.offsets[i]; |
| 171 | + } |
| 172 | + if (last_area < header.offsets[i]) |
| 173 | + { |
| 174 | + last_area = header.offsets[i]; |
| 175 | + end_of_fru = (header.offsets[i] + *(fru_data_old + (header.offsets[i] * 8) + 1)) * 8; |
| 176 | + if (header.offsets[i] == header.offset.multi) |
| 177 | + { |
| 178 | + end_of_fru = (header.offsets[i] + *(fru_data_old + (header.offsets[i] * 8) + 1)) * 8; |
| 179 | + } |
| 180 | + } |
| 181 | + if ((header.offsets[i] * 8) > header_offset) |
| 182 | + { |
| 183 | +#ifdef DBG_RESIZE_FRU |
| 184 | + printf(" moving by %i bytes.", change_size_by_8 * 8); |
| 185 | +#endif |
| 186 | + uint32_t length = *(fru_data_old + (header.offsets[i] * 8) + 1) * 8; |
| 187 | + /* MultiRecord Area length is third byte rather than second. */ |
| 188 | + if(header.offsets[i] == header.offset.multi) |
| 189 | + { |
| 190 | + length = *(fru_data_old + (header.offsets[i] * 8) + 2) * 8; |
| 191 | + } |
| 192 | + memcpy( |
| 193 | + (fru_data_new + ((header.offsets[i] + change_size_by_8) * 8)), |
| 194 | + (fru_data_old + (header.offsets[i]) * 8), |
| 195 | + length |
| 196 | + ); |
| 197 | + header.offsets[i] += change_size_by_8; |
| 198 | + } |
| 199 | +#ifdef DBG_RESIZE_FRU |
| 200 | + printf("\n"); |
| 201 | +#endif |
| 202 | } |
| 203 | - |
| 204 | - if ((f_type == 'c' ) || (f_type == 'b' ) || (f_type == 'p' )) { |
| 205 | - printf("Change multi offset from %d to %d\n", header.offset.multi, header.offset.multi + change_size_by_8); |
| 206 | - header.offset.multi += change_size_by_8; |
| 207 | + if (internal_move) |
| 208 | + { |
| 209 | + /* If the internal area is the final area in the FRU, then the only bearing |
| 210 | + * we have for the length of the FRU is the size of the FRU. |
| 211 | + */ |
| 212 | + uint32_t length = nearest_area - header.offset.internal; |
| 213 | + memcpy( |
| 214 | + (fru_data_new + ((header.offset.internal + change_size_by_8) * 8)), |
| 215 | + (fru_data_old + (header.offset.internal) * 8), |
| 216 | + length |
| 217 | + ); |
| 218 | + header.offset.internal += change_size_by_8; |
| 219 | } |
| 220 | |
| 221 | /* Adjust length of the section */ |
| 222 | @@ -5110,27 +5158,18 @@ ipmi_fru_set_field_string_rebuild(struct ipmi_intf * intf, uint8_t fruId, |
| 223 | memcpy(fru_data_new, pfru_header, sizeof(struct fru_header)); |
| 224 | } |
| 225 | |
| 226 | - /* Move remaining sections in 1 copy */ |
| 227 | - printf("Moving Remaining Bytes (Multi-Rec , etc..), from %i to %i\n", |
| 228 | - remaining_offset, |
| 229 | - ((header.offset.product) * 8) + product_len_new |
| 230 | - ); |
| 231 | - if(((header.offset.product * 8) + product_len_new - remaining_offset) < 0) |
| 232 | - { |
| 233 | - memcpy( |
| 234 | - fru_data_new + (header.offset.product * 8) + product_len_new, |
| 235 | - fru_data_old + remaining_offset, |
| 236 | - fru.size - remaining_offset |
| 237 | - ); |
| 238 | - } |
| 239 | - else |
| 240 | + /* If FRU has shrunk in size, zero-out any leftover data */ |
| 241 | + if (change_size_by_8 < 0) |
| 242 | { |
| 243 | - memcpy( |
| 244 | - fru_data_new + (header.offset.product * 8) + product_len_new, |
| 245 | - fru_data_old + remaining_offset, |
| 246 | - fru.size - ((header.offset.product * 8) + product_len_new) |
| 247 | - ); |
| 248 | + end_of_fru += change_size_by_8 * 8; |
| 249 | + int length_of_erase = change_size_by_8 * -1 * 8; |
| 250 | +#ifdef DBG_RESIZE_FRU |
| 251 | + printf("Erasing leftover data from %i to %i\n", end_of_fru, end_of_fru + length_of_erase); |
| 252 | +#endif |
| 253 | + memset(fru_data_new + end_of_fru, 0, length_of_erase); |
| 254 | } |
| 255 | + /* Step 7 assumes fru.size is the size of the new FRU. */ |
| 256 | + fru.size += (change_size_by_8 * 8); |
| 257 | } |
| 258 | |
| 259 | /* Update only if it's fits padding length as defined in the spec, otherwise, it's an internal |
| 260 | -- |
| 261 | 2.25.1 |
| 262 | |