1 /*
2  * virpcivpd.c: helper APIs for working with the PCI/PCIe VPD capability
3  *
4  * Copyright (C) 2021 Canonical Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library.  If not, see
18  * <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #ifdef __linux__
24 # include <unistd.h>
25 #endif
26 
27 #define LIBVIRT_VIRPCIVPDPRIV_H_ALLOW
28 
29 #include "virthread.h"
30 #include "virpcivpdpriv.h"
31 #include "virlog.h"
32 #include "virerror.h"
33 #include "virfile.h"
34 
35 #define VIR_FROM_THIS VIR_FROM_NONE
36 
37 VIR_LOG_INIT("util.pcivpd");
38 
39 static bool
virPCIVPDResourceIsUpperOrNumber(const char c)40 virPCIVPDResourceIsUpperOrNumber(const char c)
41 {
42     return g_ascii_isupper(c) || g_ascii_isdigit(c);
43 }
44 
45 static bool
virPCIVPDResourceIsVendorKeyword(const char * keyword)46 virPCIVPDResourceIsVendorKeyword(const char *keyword)
47 {
48     return g_str_has_prefix(keyword, "V") && virPCIVPDResourceIsUpperOrNumber(keyword[1]);
49 }
50 
51 static bool
virPCIVPDResourceIsSystemKeyword(const char * keyword)52 virPCIVPDResourceIsSystemKeyword(const char *keyword)
53 {
54     /* Special-case the system-specific keywords since they share the "Y" prefix with "YA". */
55     return (g_str_has_prefix(keyword, "Y") && virPCIVPDResourceIsUpperOrNumber(keyword[1]) &&
56             STRNEQ(keyword, "YA"));
57 }
58 
59 static char *
virPCIVPDResourceGetKeywordPrefix(const char * keyword)60 virPCIVPDResourceGetKeywordPrefix(const char *keyword)
61 {
62     g_autofree char *key = NULL;
63 
64     /* Keywords must have a length of 2 bytes. */
65     if (strlen(keyword) != 2) {
66         virReportError(VIR_ERR_INTERNAL_ERROR, _("The keyword length is not 2 bytes: %s"), keyword);
67         return NULL;
68     } else if (!(virPCIVPDResourceIsUpperOrNumber(keyword[0]) &&
69                  virPCIVPDResourceIsUpperOrNumber(keyword[1]))) {
70         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
71                        _("The keyword is not comprised only of uppercase ASCII letters or digits"));
72         return NULL;
73     }
74     /* Special-case the system-specific keywords since they share the "Y" prefix with "YA". */
75     if (virPCIVPDResourceIsSystemKeyword(keyword) || virPCIVPDResourceIsVendorKeyword(keyword))
76         key = g_strndup(keyword, 1);
77     else
78         key = g_strndup(keyword, 2);
79 
80     return g_steal_pointer(&key);
81 }
82 
83 static GHashTable *fieldValueFormats;
84 
85 static int
virPCIVPDResourceOnceInit(void)86 virPCIVPDResourceOnceInit(void)
87 {
88     /* Initialize a hash table once with static format metadata coming from the PCI(e) specs.
89      * The VPD format does not embed format metadata into the resource records so it is not
90      * possible to do format discovery without static information. Legacy PICMIG keywords
91      * are not included. NOTE: string literals are copied as g_hash_table_insert
92      * requires pointers to non-const data. */
93     fieldValueFormats = g_hash_table_new(g_str_hash, g_str_equal);
94     /* Extended capability. Contains binary data per PCI(e) specs. */
95     g_hash_table_insert(fieldValueFormats, g_strdup("CP"),
96                         GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY));
97     /* Engineering Change Level of an Add-in Card. */
98     g_hash_table_insert(fieldValueFormats, g_strdup("EC"),
99                         GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
100     /* Manufacture ID */
101     g_hash_table_insert(fieldValueFormats, g_strdup("MN"),
102                         GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
103     /* Add-in Card Part Number */
104     g_hash_table_insert(fieldValueFormats, g_strdup("PN"),
105                         GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
106     /* Checksum and Reserved */
107     g_hash_table_insert(fieldValueFormats, g_strdup("RV"),
108                         GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD));
109     /* Remaining Read/Write Area */
110     g_hash_table_insert(fieldValueFormats, g_strdup("RW"),
111                             GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR));
112     /* Serial Number */
113     g_hash_table_insert(fieldValueFormats, g_strdup("SN"),
114                         GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
115     /* Asset Tag Identifier */
116     g_hash_table_insert(fieldValueFormats, g_strdup("YA"),
117                         GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
118     /* This is a vendor specific item and the characters are alphanumeric. The second
119      * character (x) of the keyword can be 0 through Z so only the first one is stored. */
120     g_hash_table_insert(fieldValueFormats, g_strdup("V"),
121                         GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
122     /* This is a system specific item and the characters are alphanumeric.
123      * The second character (x) of the keyword can be 0 through 9 and B through Z. */
124     g_hash_table_insert(fieldValueFormats, g_strdup("Y"),
125                         GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
126 
127     return 0;
128 }
129 
130 VIR_ONCE_GLOBAL_INIT(virPCIVPDResource);
131 
132 /**
133  * virPCIVPDResourceGetFieldValueFormat:
134  * @keyword: A keyword for which to get a value type
135  *
136  * Returns: a virPCIVPDResourceFieldValueFormat value which specifies the field value type for
137  * a provided keyword based on the static information from PCI(e) specs.
138  */
139 virPCIVPDResourceFieldValueFormat
virPCIVPDResourceGetFieldValueFormat(const char * keyword)140 virPCIVPDResourceGetFieldValueFormat(const char *keyword)
141 {
142     g_autofree char *key = NULL;
143     gpointer keyVal = NULL;
144     virPCIVPDResourceFieldValueFormat format = VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
145 
146     /* Keywords are expected to be 2 bytes in length which is defined in the specs. */
147     if (strlen(keyword) != 2)
148         return VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
149 
150     if (virPCIVPDResourceInitialize() < 0)
151         return VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
152 
153     /* The system and vendor-specific keywords have a variable part - lookup
154      * the prefix significant for determining the value format. */
155     key = virPCIVPDResourceGetKeywordPrefix(keyword);
156     if (key) {
157         keyVal = g_hash_table_lookup(fieldValueFormats, key);
158         if (keyVal)
159             format = GPOINTER_TO_INT(keyVal);
160     }
161     return format;
162 }
163 
164 /**
165  * virPCIVPDResourceIsValidTextValue:
166  * @value: A NULL-terminated string to assess.
167  *
168  * Returns: a boolean indicating whether this value is a valid string resource
169  * value or text field value. The expectations are based on the keywords specified
170  * in relevant sections of PCI(e) specifications
171  * ("I.3. VPD Definitions" in PCI specs, "6.28.1 VPD Format" PCIe 4.0).
172  */
173 bool
virPCIVPDResourceIsValidTextValue(const char * value)174 virPCIVPDResourceIsValidTextValue(const char *value)
175 {
176     size_t i = 0;
177     /*
178      * The PCI(e) specs mention alphanumeric characters when talking about text fields
179      * and the string resource but also include spaces and dashes in the provided example.
180      * Dots, commas, equal signs have also been observed in values used by major device vendors.
181      * The specs do not specify a full set of allowed code points and for Libvirt it is important
182      * to keep values in the ranges allowed within XML elements (mainly excluding less-than,
183      * greater-than and ampersand).
184      */
185 
186     if (value == NULL)
187         return false;
188 
189     /* An empty string is a valid value. */
190     if (STREQ(value, ""))
191         return true;
192 
193     while (i < strlen(value)) {
194         if (!g_ascii_isprint(value[i])) {
195             VIR_DEBUG("The provided value contains non-ASCII printable characters: %s", value);
196             return false;
197         }
198         ++i;
199     }
200     return true;
201 }
202 
203 void
virPCIVPDResourceFree(virPCIVPDResource * res)204 virPCIVPDResourceFree(virPCIVPDResource *res)
205 {
206     if (!res)
207         return;
208 
209     g_free(res->name);
210     virPCIVPDResourceROFree(res->ro);
211     virPCIVPDResourceRWFree(res->rw);
212     g_free(res);
213 }
214 
215 virPCIVPDResourceRO *
virPCIVPDResourceRONew(void)216 virPCIVPDResourceRONew(void)
217 {
218     g_autoptr(virPCIVPDResourceRO) ro = g_new0(virPCIVPDResourceRO, 1);
219     ro->vendor_specific = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
220     return g_steal_pointer(&ro);
221 }
222 
223 void
virPCIVPDResourceROFree(virPCIVPDResourceRO * ro)224 virPCIVPDResourceROFree(virPCIVPDResourceRO *ro)
225 {
226     if (!ro)
227         return;
228 
229     g_free(ro->change_level);
230     g_free(ro->manufacture_id);
231     g_free(ro->part_number);
232     g_free(ro->serial_number);
233     g_ptr_array_unref(ro->vendor_specific);
234     g_free(ro);
235 }
236 
237 virPCIVPDResourceRW *
virPCIVPDResourceRWNew(void)238 virPCIVPDResourceRWNew(void)
239 {
240     g_autoptr(virPCIVPDResourceRW) rw = g_new0(virPCIVPDResourceRW, 1);
241     rw->vendor_specific = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
242     rw->system_specific = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
243     return g_steal_pointer(&rw);
244 }
245 
246 void
virPCIVPDResourceRWFree(virPCIVPDResourceRW * rw)247 virPCIVPDResourceRWFree(virPCIVPDResourceRW *rw)
248 {
249     if (!rw)
250         return;
251 
252     g_free(rw->asset_tag);
253     g_ptr_array_unref(rw->vendor_specific);
254     g_ptr_array_unref(rw->system_specific);
255     g_free(rw);
256 }
257 
258 void
virPCIVPDResourceCustomFree(virPCIVPDResourceCustom * custom)259 virPCIVPDResourceCustomFree(virPCIVPDResourceCustom *custom)
260 {
261     g_free(custom->value);
262     g_free(custom);
263 }
264 
265 gboolean
virPCIVPDResourceCustomCompareIndex(virPCIVPDResourceCustom * a,virPCIVPDResourceCustom * b)266 virPCIVPDResourceCustomCompareIndex(virPCIVPDResourceCustom *a, virPCIVPDResourceCustom *b)
267 {
268     if (a == b)
269         return TRUE;
270     else if (a == NULL || b == NULL)
271         return FALSE;
272     else
273         return a->idx == b->idx ? TRUE : FALSE;
274 }
275 
276 /**
277  * virPCIVPDResourceCustomUpsertValue:
278  * @arr: A GPtrArray with virPCIVPDResourceCustom entries to update.
279  * @index: An index character for the keyword.
280  * @value: A pointer to the value to be inserted at a given index.
281  *
282  * Returns: true if a value has been updated successfully, false otherwise.
283  */
284 bool
virPCIVPDResourceCustomUpsertValue(GPtrArray * arr,char index,const char * const value)285 virPCIVPDResourceCustomUpsertValue(GPtrArray *arr, char index, const char *const value)
286 {
287     g_autoptr(virPCIVPDResourceCustom) custom = NULL;
288     virPCIVPDResourceCustom *existing = NULL;
289     guint pos = 0;
290     bool found = false;
291 
292     if (arr == NULL || value == NULL)
293         return false;
294 
295     custom = g_new0(virPCIVPDResourceCustom, 1);
296     custom->idx = index;
297     custom->value = g_strdup(value);
298     found = g_ptr_array_find_with_equal_func(arr, custom,
299                                              (GEqualFunc)virPCIVPDResourceCustomCompareIndex,
300                                              &pos);
301     if (found) {
302         existing = g_ptr_array_index(arr, pos);
303         g_free(existing->value);
304         existing->value = g_steal_pointer(&custom->value);
305     } else {
306         g_ptr_array_add(arr, g_steal_pointer(&custom));
307     }
308     return true;
309 }
310 
311 /**
312  * virPCIVPDResourceUpdateKeyword:
313  * @res: A non-NULL pointer to a virPCIVPDResource where a keyword will be updated.
314  * @readOnly: A bool specifying which section to update (in-memory): read-only or read-write.
315  * @keyword: A non-NULL pointer to a name of the keyword that will be updated.
316  * @value: A pointer to the keyword value or NULL. The value is copied on successful update.
317  *
318  * The caller is responsible for initializing the relevant RO or RW sections of the resource,
319  * otherwise, false will be returned.
320  *
321  * Keyword names are either 2-byte keywords from the spec or their human-readable alternatives
322  * used in XML elements. For vendor-specific and system-specific keywords only V%s and Y%s
323  * (except "YA" which is an asset tag) formatted values are accepted.
324  *
325  * Returns: true if a keyword has been updated successfully, false otherwise.
326  */
327 bool
virPCIVPDResourceUpdateKeyword(virPCIVPDResource * res,const bool readOnly,const char * const keyword,const char * const value)328 virPCIVPDResourceUpdateKeyword(virPCIVPDResource *res, const bool readOnly,
329                                const char *const keyword, const char *const value)
330 {
331     if (!res) {
332         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
333                        _("Cannot update the resource: a NULL resource pointer has been provided."));
334         return false;
335     } else if (!keyword) {
336         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
337                        _("Cannot update the resource: a NULL keyword pointer has been provided."));
338         return false;
339     }
340 
341     if (readOnly) {
342         if (!res->ro) {
343             virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
344                            _("Cannot update the read-only keyword: RO section not initialized."));
345             return false;
346         }
347 
348         if (STREQ("EC", keyword) || STREQ("change_level", keyword)) {
349             g_free(res->ro->change_level);
350             res->ro->change_level = g_strdup(value);
351             return true;
352         } else if (STREQ("MN", keyword) || STREQ("manufacture_id", keyword)) {
353             g_free(res->ro->manufacture_id);
354             res->ro->manufacture_id = g_strdup(value);
355             return true;
356         } else if (STREQ("PN", keyword) || STREQ("part_number", keyword)) {
357             g_free(res->ro->part_number);
358             res->ro->part_number = g_strdup(value);
359             return true;
360         } else if (STREQ("SN", keyword) || STREQ("serial_number", keyword)) {
361             g_free(res->ro->serial_number);
362             res->ro->serial_number = g_strdup(value);
363             return true;
364         } else if (virPCIVPDResourceIsVendorKeyword(keyword)) {
365             if (!virPCIVPDResourceCustomUpsertValue(res->ro->vendor_specific, keyword[1], value)) {
366                 return false;
367             }
368             return true;
369         } else if (STREQ("FG", keyword) || STREQ("LC", keyword) || STREQ("PG", keyword)) {
370             /* Legacy PICMIG keywords are skipped on purpose. */
371             return true;
372         } else if (STREQ("CP", keyword)) {
373             /* The CP keyword is currently not supported and is skipped. */
374             return true;
375         }
376 
377     } else {
378         if (!res->rw) {
379             virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
380                            _
381                            ("Cannot update the read-write keyword: read-write section not initialized."));
382             return false;
383         }
384 
385         if (STREQ("YA", keyword) || STREQ("asset_tag", keyword)) {
386             g_free(res->rw->asset_tag);
387             res->rw->asset_tag = g_strdup(value);
388             return true;
389         } else if (virPCIVPDResourceIsVendorKeyword(keyword)) {
390             if (!virPCIVPDResourceCustomUpsertValue(res->rw->vendor_specific, keyword[1], value)) {
391                 return false;
392             }
393             return true;
394         } else if (virPCIVPDResourceIsSystemKeyword(keyword)) {
395             if (!virPCIVPDResourceCustomUpsertValue(res->rw->system_specific, keyword[1], value)) {
396                 return false;
397             }
398             return true;
399         }
400     }
401     VIR_WARN("Tried to update an unsupported keyword %s: skipping.", keyword);
402     return true;
403 }
404 
405 #ifdef __linux__
406 
407 /**
408  * virPCIVPDReadVPDBytes:
409  * @vpdFileFd: A file descriptor associated with a file containing PCI device VPD.
410  * @buf: An allocated buffer to use for storing VPD bytes read.
411  * @count: The number of bytes to read from the VPD file descriptor.
412  * @offset: The offset at which bytes need to be read.
413  * @csum: A pointer to a byte containing the current checksum value. Mutated by this function.
414  *
415  * Returns: the number of VPD bytes read from the specified file descriptor. The csum value is
416  * also modified as bytes are read. If an error occurs while reading data from the VPD file
417  * descriptor, it is reported and -1 is returned to the caller. If EOF is occurred, 0 is returned
418  * to the caller.
419  */
420 size_t
virPCIVPDReadVPDBytes(int vpdFileFd,uint8_t * buf,size_t count,off_t offset,uint8_t * csum)421 virPCIVPDReadVPDBytes(int vpdFileFd, uint8_t *buf, size_t count, off_t offset, uint8_t *csum)
422 {
423     ssize_t numRead = pread(vpdFileFd, buf, count, offset);
424 
425     if (numRead == -1) {
426         VIR_DEBUG("Unable to read %zu bytes at offset %zd from fd: %d",
427                   count, (ssize_t)offset, vpdFileFd);
428     } else if (numRead) {
429         /*
430          * Update the checksum for every byte read. Per the PCI(e) specs
431          * the checksum is correct if the sum of all bytes in VPD from
432          * VPD address 0 up to and including the VPD-R RV field's first
433          * data byte is zero.
434          */
435         while (count--) {
436             *csum += *buf;
437             buf++;
438         }
439     }
440     return numRead;
441 }
442 
443 /**
444  * virPCIVPDParseVPDLargeResourceFields:
445  * @vpdFileFd: A file descriptor associated with a file containing PCI device VPD.
446  * @resPos: A position where the resource data bytes begin in a file descriptor.
447  * @resDataLen: A length of the data portion of a resource.
448  * @readOnly: A boolean showing whether the resource type is VPD-R or VPD-W.
449  * @csum: A pointer to a 1-byte checksum.
450  * @res: A pointer to virPCIVPDResource.
451  *
452  * Returns: a pointer to a VPDResource which needs to be freed by the caller or
453  * NULL if getting it failed for some reason.
454  */
455 bool
virPCIVPDParseVPDLargeResourceFields(int vpdFileFd,uint16_t resPos,uint16_t resDataLen,bool readOnly,uint8_t * csum,virPCIVPDResource * res)456 virPCIVPDParseVPDLargeResourceFields(int vpdFileFd, uint16_t resPos, uint16_t resDataLen,
457                                      bool readOnly, uint8_t *csum, virPCIVPDResource *res)
458 {
459     /* A buffer of up to one resource record field size (plus a zero byte) is needed. */
460     g_autofree uint8_t *buf = g_malloc0(PCI_VPD_MAX_FIELD_SIZE + 1);
461     uint16_t fieldDataLen = 0, bytesToRead = 0;
462     uint16_t fieldPos = resPos;
463 
464     bool hasChecksum = false;
465     bool hasRW = false;
466     bool endReached = false;
467 
468     /* Note the equal sign - fields may have a zero length in which case they will
469      * just occupy 3 header bytes. In the in case of the RW field this may mean that
470      * no more space is left in the section. */
471     while (fieldPos + 3 <= resPos + resDataLen) {
472         virPCIVPDResourceFieldValueFormat fieldFormat = VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
473         g_autofree char *fieldKeyword = NULL;
474         g_autofree char *fieldValue = NULL;
475 
476         /* Keyword resources consist of keywords (2 ASCII bytes per the spec) and 1-byte length. */
477         if (virPCIVPDReadVPDBytes(vpdFileFd, buf, 3, fieldPos, csum) != 3) {
478             /* Invalid field encountered which means the resource itself is invalid too. Report
479              * That VPD has invalid format and bail. */
480             virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
481                            _("Could not read a resource field header - VPD has invalid format"));
482             return false;
483         }
484         fieldDataLen = buf[2];
485         /* Change the position to the field's data portion skipping the keyword and length bytes. */
486         fieldPos += 3;
487         fieldKeyword = g_strndup((char *)buf, 2);
488         fieldFormat = virPCIVPDResourceGetFieldValueFormat(fieldKeyword);
489 
490         /* Handle special cases first */
491         if (!readOnly && fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD) {
492             virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
493                            _("Unexpected RV keyword in the read-write section."));
494             return false;
495         } else if (readOnly && fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR) {
496             virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
497                            _("Unexpected RW keyword in the read-only section."));
498             return false;
499         }
500 
501         /* Determine how many bytes to read per field value type. */
502         switch (fieldFormat) {
503             case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT:
504             case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR:
505             case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY:
506                 bytesToRead = fieldDataLen;
507                 break;
508             case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD:
509                 /* Only need one byte to be read and accounted towards
510                  * the checksum calculation. */
511                 bytesToRead = 1;
512                 break;
513             case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST:
514                 /* The VPD format could be extended in future versions with new
515                  * keywords - attempt to skip them by reading past them since
516                  * their data length would still be specified. */
517                 VIR_DEBUG("Could not determine a field value format for keyword: %s", fieldKeyword);
518                 bytesToRead = fieldDataLen;
519                 break;
520             default:
521                 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
522                                _("Unexpected field value format encountered."));
523                 return false;
524         }
525 
526         if (resPos + resDataLen < fieldPos + fieldDataLen) {
527             /* In this case the field cannot simply be skipped since the position of the
528              * next field is determined based on the length of a previous field. */
529             virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
530                            _("A field data length violates the resource length boundary."));
531             return false;
532         }
533         if (virPCIVPDReadVPDBytes(vpdFileFd, buf, bytesToRead, fieldPos, csum) != bytesToRead) {
534             virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
535                            _("Could not parse a resource field data - VPD has invalid format"));
536             return false;
537         }
538         /* Advance the position to the first byte of the next field. */
539         fieldPos += fieldDataLen;
540 
541         if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT) {
542             /* Trim whitespace around a retrieved value and set it to be a field's value. Cases
543              * where unnecessary whitespace was present around a field value have been encountered
544              * in the wild.
545              */
546             fieldValue = g_strstrip(g_strndup((char *)buf, fieldDataLen));
547             if (!virPCIVPDResourceIsValidTextValue(fieldValue)) {
548                 /* Skip fields with invalid values - this is safe assuming field length is
549                  * correctly specified. */
550                 VIR_DEBUG("A value for field %s contains invalid characters", fieldKeyword);
551                 continue;
552             }
553         } else if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD) {
554             if (*csum) {
555                 /* All bytes up to and including the checksum byte should add up to 0. */
556                 virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Checksum validation has failed"));
557                 return false;
558             }
559             hasChecksum = true;
560             break;
561         } else if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR) {
562             /* Skip the read-write space since it is used for indication only. */
563             hasRW = true;
564             break;
565         } else if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST) {
566             /* Skip unknown fields */
567             continue;
568         } else {
569             fieldValue = g_malloc(fieldDataLen);
570             memcpy(fieldValue, buf, fieldDataLen);
571         }
572 
573         if (readOnly) {
574             if (!res->ro)
575                 res->ro = virPCIVPDResourceRONew();
576         } else {
577             if (!res->rw)
578                 res->rw = virPCIVPDResourceRWNew();
579         }
580         /* The field format, keyword and value are determined. Attempt to update the resource. */
581         if (!virPCIVPDResourceUpdateKeyword(res, readOnly, fieldKeyword, fieldValue)) {
582             virReportError(VIR_ERR_INTERNAL_ERROR,
583                            _("Could not update the VPD resource keyword: %s"), fieldKeyword);
584             return false;
585         }
586     }
587 
588     /* May have exited the loop prematurely in case RV or RW were encountered and
589      * they were not the last fields in the section. */
590     endReached = (fieldPos >= resPos + resDataLen);
591     if (readOnly && !(hasChecksum && endReached)) {
592         VIR_DEBUG("VPD-R does not contain the mandatory RV field as the last field");
593         return false;
594     } else if (!readOnly && !endReached) {
595         /* The lack of RW is allowed on purpose in the read-write section since some vendors
596          * violate the PCI/PCIe specs and do not include it, however, this does not prevent parsing
597          * of valid data. If the RW is present, however, we make sure it is the last field in
598          * the read-write section. */
599         if (hasRW) {
600             VIR_DEBUG("VPD-W section parsing ended prematurely (RW is not the last field).");
601             return false;
602         } else {
603             VIR_DEBUG("VPD-W section parsing ended prematurely.");
604             return false;
605         }
606     }
607 
608     return true;
609 }
610 
611 /**
612  * virPCIVPDParseVPDLargeResourceString:
613  * @vpdFileFd: A file descriptor associated with a file containing PCI device VPD.
614  * @resPos: A position where the resource data bytes begin in a file descriptor.
615  * @resDataLen: A length of the data portion of a resource.
616  * @csum: A pointer to a 1-byte checksum.
617  *
618  * Returns: a pointer to a VPDResource which needs to be freed by the caller or
619  * NULL if getting it failed for some reason.
620  */
621 bool
virPCIVPDParseVPDLargeResourceString(int vpdFileFd,uint16_t resPos,uint16_t resDataLen,uint8_t * csum,virPCIVPDResource * res)622 virPCIVPDParseVPDLargeResourceString(int vpdFileFd, uint16_t resPos,
623                                      uint16_t resDataLen, uint8_t *csum, virPCIVPDResource *res)
624 {
625     g_autofree char *resValue = NULL;
626 
627     /* The resource value is not NULL-terminated so add one more byte. */
628     g_autofree char *buf = g_malloc0(resDataLen + 1);
629 
630     if (virPCIVPDReadVPDBytes(vpdFileFd, (uint8_t *)buf, resDataLen, resPos, csum) != resDataLen) {
631         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
632                        _("Could not read a part of a resource - VPD has invalid format"));
633         return false;
634     }
635     resValue = g_strdup(g_strstrip(buf));
636     if (!virPCIVPDResourceIsValidTextValue(resValue)) {
637         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
638                        _("The string resource has invalid characters in its value"));
639         return false;
640     }
641     res->name = g_steal_pointer(&resValue);
642     return true;
643 }
644 
645 /**
646  * virPCIVPDParse:
647  * @vpdFileFd: a file descriptor associated with a file containing PCI device VPD.
648  *
649  * Parse a PCI device's Vital Product Data (VPD) contained in a file descriptor.
650  *
651  * Returns: a pointer to a GList of VPDResource types which needs to be freed by the caller or
652  * NULL if getting it failed for some reason.
653  */
654 virPCIVPDResource *
virPCIVPDParse(int vpdFileFd)655 virPCIVPDParse(int vpdFileFd)
656 {
657     /* A checksum which is calculated as a sum of all bytes from VPD byte 0 up to
658      * the checksum byte in the RV field's value. The RV field is only present in the
659      * VPD-R resource and the checksum byte there is the first byte of the field's value.
660      * The checksum byte in RV field is actually a two's complement of the sum of all bytes
661      * of VPD that come before it so adding the two together must produce 0 if data
662      * was not corrupted and VPD storage is intact.
663      */
664     uint8_t csum = 0;
665     uint8_t headerBuf[2];
666 
667     bool isWellFormed = false;
668     uint16_t resPos = 0, resDataLen;
669     uint8_t tag = 0;
670     bool endResReached = false, hasReadOnly = false;
671 
672     g_autoptr(virPCIVPDResource) res = g_new0(virPCIVPDResource, 1);
673 
674     while (resPos <= PCI_VPD_ADDR_MASK) {
675         /* Read the resource data type tag. */
676         if (virPCIVPDReadVPDBytes(vpdFileFd, &tag, 1, resPos, &csum) != 1)
677             break;
678 
679         /* 0x80 == 0b10000000 - the large resource data type flag. */
680         if (tag & PCI_VPD_LARGE_RESOURCE_FLAG) {
681             if (resPos > PCI_VPD_ADDR_MASK + 1 - 3) {
682                 /* Bail if the large resource starts at the position
683                  * where the end tag should be. */
684                 break;
685             }
686             /* Read the two length bytes of the large resource record. */
687             if (virPCIVPDReadVPDBytes(vpdFileFd, headerBuf, 2, resPos + 1, &csum) != 2)
688                 break;
689 
690             resDataLen = headerBuf[0] + (headerBuf[1] << 8);
691             /* Change the position to the byte following the tag and length bytes. */
692             resPos += 3;
693         } else {
694             /* Handle a small resource record.
695              * 0xxxxyyy & 00000111, where xxxx - resource data type bits, yyy - length bits. */
696             resDataLen = tag & 7;
697             /* 0xxxxyyy >> 3 == 0000xxxx */
698             tag >>= 3;
699             /* Change the position to the byte past the byte containing tag and length bits. */
700             resPos += 1;
701         }
702         if (tag == PCI_VPD_RESOURCE_END_TAG) {
703             /* Stop VPD traversal since the end tag was encountered. */
704             endResReached = true;
705             break;
706         }
707         if (resDataLen > PCI_VPD_ADDR_MASK + 1 - resPos) {
708             /* Bail if the resource is too long to fit into the VPD address space. */
709             break;
710         }
711 
712         switch (tag) {
713                 /* Large resource type which is also a string: 0x80 | 0x02 = 0x82 */
714             case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG:
715                 isWellFormed = virPCIVPDParseVPDLargeResourceString(vpdFileFd, resPos, resDataLen,
716                                                                     &csum, res);
717                 break;
718                 /* Large resource type which is also a VPD-R: 0x80 | 0x10 == 0x90 */
719             case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG:
720                 isWellFormed = virPCIVPDParseVPDLargeResourceFields(vpdFileFd, resPos,
721                                                                     resDataLen, true, &csum, res);
722                 /* Encountered the VPD-R tag. The resource record parsing also validates
723                  * the presence of the required checksum in the RV field. */
724                 hasReadOnly = true;
725                 break;
726                 /* Large resource type which is also a VPD-W: 0x80 | 0x11 == 0x91 */
727             case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG:
728                 isWellFormed = virPCIVPDParseVPDLargeResourceFields(vpdFileFd, resPos, resDataLen,
729                                                                     false, &csum, res);
730                 break;
731             default:
732                 /* While we cannot parse unknown resource types, they can still be skipped
733                  * based on the header and data length. */
734                 VIR_DEBUG("Encountered an unexpected VPD resource tag: %#x", tag);
735                 resPos += resDataLen;
736                 continue;
737         }
738 
739         if (!isWellFormed) {
740             VIR_DEBUG("Encountered an invalid VPD");
741             return NULL;
742         }
743 
744         /* Continue processing other resource records. */
745         resPos += resDataLen;
746     }
747     if (!hasReadOnly) {
748         VIR_DEBUG("Encountered an invalid VPD: does not have a VPD-R record");
749         return NULL;
750     } else if (!endResReached) {
751         /* Does not have an end tag. */
752         VIR_DEBUG("Encountered an invalid VPD");
753         return NULL;
754     }
755     return g_steal_pointer(&res);
756 }
757 
758 #else /* ! __linux__ */
759 
760 size_t
virPCIVPDReadVPDBytes(int vpdFileFd G_GNUC_UNUSED,uint8_t * buf G_GNUC_UNUSED,size_t count G_GNUC_UNUSED,off_t offset G_GNUC_UNUSED,uint8_t * csum G_GNUC_UNUSED)761 virPCIVPDReadVPDBytes(int vpdFileFd G_GNUC_UNUSED,
762                       uint8_t *buf G_GNUC_UNUSED,
763                       size_t count G_GNUC_UNUSED,
764                       off_t offset G_GNUC_UNUSED,
765                       uint8_t *csum G_GNUC_UNUSED)
766 {
767     virReportError(VIR_ERR_NO_SUPPORT, "%s",
768                    _("PCI VPD reporting not available on this platform"));
769     return 0;
770 }
771 
772 bool
virPCIVPDParseVPDLargeResourceString(int vpdFileFd G_GNUC_UNUSED,uint16_t resPos G_GNUC_UNUSED,uint16_t resDataLen G_GNUC_UNUSED,uint8_t * csum G_GNUC_UNUSED,virPCIVPDResource * res G_GNUC_UNUSED)773 virPCIVPDParseVPDLargeResourceString(int vpdFileFd G_GNUC_UNUSED,
774                                      uint16_t resPos G_GNUC_UNUSED,
775                                      uint16_t resDataLen G_GNUC_UNUSED,
776                                      uint8_t *csum G_GNUC_UNUSED,
777                                      virPCIVPDResource *res G_GNUC_UNUSED)
778 {
779     virReportError(VIR_ERR_NO_SUPPORT, "%s",
780                    _("PCI VPD reporting not available on this platform"));
781     return false;
782 }
783 
784 bool
virPCIVPDParseVPDLargeResourceFields(int vpdFileFd G_GNUC_UNUSED,uint16_t resPos G_GNUC_UNUSED,uint16_t resDataLen G_GNUC_UNUSED,bool readOnly G_GNUC_UNUSED,uint8_t * csum G_GNUC_UNUSED,virPCIVPDResource * res G_GNUC_UNUSED)785 virPCIVPDParseVPDLargeResourceFields(int vpdFileFd G_GNUC_UNUSED,
786                                      uint16_t resPos G_GNUC_UNUSED,
787                                      uint16_t resDataLen G_GNUC_UNUSED,
788                                      bool readOnly G_GNUC_UNUSED,
789                                      uint8_t *csum G_GNUC_UNUSED,
790                                      virPCIVPDResource *res G_GNUC_UNUSED)
791 {
792     virReportError(VIR_ERR_NO_SUPPORT, "%s",
793                    _("PCI VPD reporting not available on this platform"));
794     return false;
795 }
796 
797 virPCIVPDResource *
virPCIVPDParse(int vpdFileFd G_GNUC_UNUSED)798 virPCIVPDParse(int vpdFileFd G_GNUC_UNUSED)
799 {
800     virReportError(VIR_ERR_NO_SUPPORT, "%s",
801                    _("PCI VPD reporting not available on this platform"));
802     return NULL;
803 }
804 
805 #endif /* ! __linux__ */
806