File: drgn_program_parse_vmcoreinfo.inc.strswitch

package info (click to toggle)
drgn 0.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 7,852 kB
  • sloc: python: 74,992; ansic: 54,589; awk: 423; makefile: 351; sh: 99
file content (211 lines) | stat: -rw-r--r-- 6,486 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later

static struct drgn_error *parse_vmcoreinfo_u64(const char *value,
					       const char *newline, int base,
					       uint64_t *ret)
{
	errno = 0;
	char *end;
	*ret = strtoull(value, &end, base);
	if (errno == ERANGE) {
		return drgn_error_create(DRGN_ERROR_OVERFLOW,
					 "number in VMCOREINFO is too large");
	} else if (errno || end == value || end != newline) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "number in VMCOREINFO is invalid");
	}
	return NULL;
}

// Linux kernel commit 905415ff3ffb ("lib/buildid: harden build ID parsing
// logic") (in v6.12) contains a bug that results in a garbage build ID in
// VMCOREINFO. It was fixed in the same patch series in commits de3ec364c3c3
// ("lib/buildid: add single folio-based file reader abstraction") and
// d4deb8242341 ("lib/buildid: take into account e_phoff when fetching program
// headers"). However, the broken commit was backported to several stable
// kernels. Some branches were fixed by "lib/buildid: Fix build ID parsing
// logic", but a couple reached their end-of-life while broken. See
// https://lore.kernel.org/all/20241104175256.2327164-1-jolsa@kernel.org/.
//
// The very sad workaround is to ignore the build ID based on a version check.
static void ignore_broken_vmcoreinfo_build_id(struct drgn_program *prog)
{
	char *p = (char *)prog->vmcoreinfo.osrelease;
	long major = strtol(p, &p, 10), minor = 0, patch = 0;
	if (*p == '.') {
		minor = strtol(p + 1, &p, 10);
		if (*p == '.')
			patch = strtol(p + 1, NULL, 10);
	}
	if ((major == 6 && minor == 11 && patch >= 3 && patch < 10)
	    || (major == 6 && minor == 10 && patch >= 14)
	    || (major == 6 && minor == 6 && patch >= 55 && patch < 63)
	    || (major == 6 && minor == 1 && patch >= 113 && patch < 119)
	    || (major == 5 && minor == 15 && patch >= 168))
		prog->vmcoreinfo.build_id_len = 0;
}

struct drgn_error *drgn_program_parse_vmcoreinfo(struct drgn_program *prog,
						 const char *desc,
						 size_t descsz)
{
	struct drgn_error *err;

	prog->vmcoreinfo.raw_size = descsz;
	prog->vmcoreinfo.raw = memdup(desc, descsz);
	if (!prog->vmcoreinfo.raw)
		return &drgn_enomem;
	for (const char *line = desc, *end = &desc[descsz], *newline;
	     (newline = memchr(line, '\n', end - line));
	     line = newline + 1) {
		const char *equals = memchr(line, '=', newline - line);
		if (!equals)
			continue;

		const char *value = equals + 1;
		@memswitch (line, equals - line)@
		@case "BUILD-ID"@
		{
			size_t build_id_len = (newline - value) / 2;
			if (build_id_len > sizeof(prog->vmcoreinfo.build_id)) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "BUILD-ID in VMCOREINFO is too long");
			}
			if (!unhexlify(value, newline - value,
				       &prog->vmcoreinfo.build_id)) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "couldn't parse BUILD-ID in VMCOREINFO");
			}
			prog->vmcoreinfo.build_id_len = build_id_len;
			break;
		}
		@case "CRASHTIME"@
			prog->vmcoreinfo.have_crashtime = true;
			break;
		@case "OSRELEASE"@
			if ((size_t)(newline - value) >=
			    sizeof(prog->vmcoreinfo.osrelease)) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "OSRELEASE in VMCOREINFO is too long");
			}
			memcpy(prog->vmcoreinfo.osrelease, value,
			       newline - value);
			prog->vmcoreinfo.osrelease[newline - value] = '\0';
			break;
		@case "PAGESIZE"@
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.page_size);
			if (err)
				return err;
			break;
		@case "KERNELOFFSET"@
			err = parse_vmcoreinfo_u64(value, newline, 16,
						   &prog->vmcoreinfo.kaslr_offset);
			if (err)
				return err;
			break;
		@case "SYMBOL(swapper_pg_dir)"@
			err = parse_vmcoreinfo_u64(value, newline, 16,
						   &prog->vmcoreinfo.swapper_pg_dir);
			if (err)
				return err;
			break;
		@case "LENGTH(mem_section)"@
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.mem_section_length);
			if (err)
				return err;
			break;
		@case "NUMBER(SECTION_SIZE_BITS)"@
		{
			uint64_t tmp;
			err = parse_vmcoreinfo_u64(value, newline, 0, &tmp);
			if (err)
				return err;
			if (tmp == 0 || tmp > 64) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "SECTION_SIZE_BITS in VMCOREINFO is invalid");
			}
			prog->vmcoreinfo.section_size_bits = tmp;
			break;
		}
		@case "NUMBER(MAX_PHYSMEM_BITS)"@
		{
			uint64_t tmp;
			err = parse_vmcoreinfo_u64(value, newline, 0, &tmp);
			if (err)
				return err;
			if (tmp == 0 || tmp > 64) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "MAX_PHYSMEM_BITS in VMCOREINFO is invalid");
			}
			prog->vmcoreinfo.max_physmem_bits = tmp;
			break;
		}
		@case "NUMBER(pgtable_l5_enabled)"@
		{
			uint64_t tmp;
			err = parse_vmcoreinfo_u64(value, newline, 0, &tmp);
			if (err)
				return err;
			prog->vmcoreinfo.pgtable_l5_enabled = tmp;
			break;
		}
		@case "NUMBER(kimage_voffset)"@
		{
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.kimage_voffset);
			if (err)
				return err;
			break;
		}
		@case "NUMBER(phys_base)"@
		{
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.phys_base);
			if (err)
				return err;
			prog->vmcoreinfo.have_phys_base = true;
			break;
		}
		@case "NUMBER(KERNELPACMASK)"@
			err = parse_vmcoreinfo_u64(value, newline, 16,
						   &prog->aarch64_insn_pac_mask);
			if (err)
				return err;
			break;
		@case "NUMBER(VA_BITS)"@
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.va_bits);
			if (err)
				return err;
			break;
		@case "NUMBER(TCR_EL1_T1SZ)"@
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.tcr_el1_t1sz);
			if (err)
				return err;
			break;
		@case "CONFIG_ARM_LPAE"@
			prog->vmcoreinfo.arm_lpae = value[0] == 'y';
			break;
		@endswitch@
	}
	if (!prog->vmcoreinfo.osrelease[0]) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "VMCOREINFO does not contain valid OSRELEASE");
	}
	ignore_broken_vmcoreinfo_build_id(prog);
	if (!is_power_of_two(prog->vmcoreinfo.page_size)) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "VMCOREINFO does not contain valid PAGESIZE");
	}
	prog->vmcoreinfo.page_shift = ctz(prog->vmcoreinfo.page_size);
	if (!prog->vmcoreinfo.swapper_pg_dir) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "VMCOREINFO does not contain valid swapper_pg_dir");
	}
	// Everything else is optional.
	return NULL;
}