1// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
2/*
3 * libfdt - Flat Device Tree manipulation
4 * Copyright (C) 2006 David Gibson, IBM Corporation.
5 */
6#include "libfdt_env.h"
7
8#include <fdt.h>
9#include <libfdt.h>
10
11#include "libfdt_internal.h"
12
13/*
14 * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks
15 * that the given buffer contains what appears to be a flattened
16 * device tree with sane information in its header.
17 */
18int32_t fdt_ro_probe_(const void *fdt)
19{
20	uint32_t totalsize = fdt_totalsize(fdt);
21
22	if (fdt_magic(fdt) == FDT_MAGIC) {
23		/* Complete tree */
24		if (fdt_chk_version()) {
25			if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION)
26				return -FDT_ERR_BADVERSION;
27			if (fdt_last_comp_version(fdt) >
28					FDT_LAST_SUPPORTED_VERSION)
29				return -FDT_ERR_BADVERSION;
30		}
31	} else if (fdt_magic(fdt) == FDT_SW_MAGIC) {
32		/* Unfinished sequential-write blob */
33		if (fdt_size_dt_struct(fdt) == 0)
34			return -FDT_ERR_BADSTATE;
35	} else {
36		return -FDT_ERR_BADMAGIC;
37	}
38
39	if (totalsize < INT32_MAX)
40		return totalsize;
41	else
42		return -FDT_ERR_TRUNCATED;
43}
44
45static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off)
46{
47	return (off >= hdrsize) && (off <= totalsize);
48}
49
50static int check_block_(uint32_t hdrsize, uint32_t totalsize,
51			uint32_t base, uint32_t size)
52{
53	if (!check_off_(hdrsize, totalsize, base))
54		return 0; /* block start out of bounds */
55	if ((base + size) < base)
56		return 0; /* overflow */
57	if (!check_off_(hdrsize, totalsize, base + size))
58		return 0; /* block end out of bounds */
59	return 1;
60}
61
62size_t fdt_header_size_(uint32_t version)
63{
64	if (version <= 1)
65		return FDT_V1_SIZE;
66	else if (version <= 2)
67		return FDT_V2_SIZE;
68	else if (version <= 3)
69		return FDT_V3_SIZE;
70	else if (version <= 16)
71		return FDT_V16_SIZE;
72	else
73		return FDT_V17_SIZE;
74}
75
76size_t fdt_header_size(const void *fdt)
77{
78	return fdt_chk_version() ? fdt_header_size_(fdt_version(fdt)) :
79		FDT_V17_SIZE;
80}
81
82int fdt_check_header(const void *fdt)
83{
84	size_t hdrsize;
85
86	if (fdt_magic(fdt) != FDT_MAGIC)
87		return -FDT_ERR_BADMAGIC;
88	if (fdt_chk_version()) {
89		if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION)
90		    || (fdt_last_comp_version(fdt) >
91			FDT_LAST_SUPPORTED_VERSION))
92			return -FDT_ERR_BADVERSION;
93		if (fdt_version(fdt) < fdt_last_comp_version(fdt))
94			return -FDT_ERR_BADVERSION;
95	}
96	hdrsize = fdt_header_size(fdt);
97	if (fdt_chk_basic()) {
98
99		if ((fdt_totalsize(fdt) < hdrsize)
100		    || (fdt_totalsize(fdt) > INT_MAX))
101			return -FDT_ERR_TRUNCATED;
102
103		/* Bounds check memrsv block */
104		if (!check_off_(hdrsize, fdt_totalsize(fdt),
105				fdt_off_mem_rsvmap(fdt)))
106			return -FDT_ERR_TRUNCATED;
107	}
108
109	if (fdt_chk_extra()) {
110		/* Bounds check structure block */
111		if (fdt_chk_version() && fdt_version(fdt) < 17) {
112			if (!check_off_(hdrsize, fdt_totalsize(fdt),
113					fdt_off_dt_struct(fdt)))
114				return -FDT_ERR_TRUNCATED;
115		} else {
116			if (!check_block_(hdrsize, fdt_totalsize(fdt),
117					  fdt_off_dt_struct(fdt),
118					  fdt_size_dt_struct(fdt)))
119				return -FDT_ERR_TRUNCATED;
120		}
121
122		/* Bounds check strings block */
123		if (!check_block_(hdrsize, fdt_totalsize(fdt),
124				  fdt_off_dt_strings(fdt),
125				  fdt_size_dt_strings(fdt)))
126			return -FDT_ERR_TRUNCATED;
127	}
128
129	return 0;
130}
131
132const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len)
133{
134	unsigned int uoffset = offset;
135	unsigned int absoffset = offset + fdt_off_dt_struct(fdt);
136
137	if (offset < 0)
138		return NULL;
139
140	if (fdt_chk_basic())
141		if ((absoffset < uoffset)
142		    || ((absoffset + len) < absoffset)
143		    || (absoffset + len) > fdt_totalsize(fdt))
144			return NULL;
145
146	if (!fdt_chk_version() || fdt_version(fdt) >= 0x11)
147		if (((uoffset + len) < uoffset)
148		    || ((offset + len) > fdt_size_dt_struct(fdt)))
149			return NULL;
150
151	return fdt_offset_ptr_(fdt, offset);
152}
153
154uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
155{
156	const fdt32_t *tagp, *lenp;
157	uint32_t tag;
158	int offset = startoffset;
159	const char *p;
160
161	*nextoffset = -FDT_ERR_TRUNCATED;
162	tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE);
163	if (fdt_chk_basic() && !tagp)
164		return FDT_END; /* premature end */
165	tag = fdt32_to_cpu(*tagp);
166	offset += FDT_TAGSIZE;
167
168	*nextoffset = -FDT_ERR_BADSTRUCTURE;
169	switch (tag) {
170	case FDT_BEGIN_NODE:
171		/* skip name */
172		do {
173			p = fdt_offset_ptr(fdt, offset++, 1);
174		} while (p && (*p != '\0'));
175		if (fdt_chk_basic() && !p)
176			return FDT_END; /* premature end */
177		break;
178
179	case FDT_PROP:
180		lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp));
181		if (fdt_chk_basic() && !lenp)
182			return FDT_END; /* premature end */
183		/* skip-name offset, length and value */
184		offset += sizeof(struct fdt_property) - FDT_TAGSIZE
185			+ fdt32_to_cpu(*lenp);
186		if (fdt_chk_version() &&
187		    fdt_version(fdt) < 0x10 && fdt32_to_cpu(*lenp) >= 8 &&
188		    ((offset - fdt32_to_cpu(*lenp)) % 8) != 0)
189			offset += 4;
190		break;
191
192	case FDT_END:
193	case FDT_END_NODE:
194	case FDT_NOP:
195		break;
196
197	default:
198		return FDT_END;
199	}
200
201	if (fdt_chk_basic() &&
202	    !fdt_offset_ptr(fdt, startoffset, offset - startoffset))
203		return FDT_END; /* premature end */
204
205	*nextoffset = FDT_TAGALIGN(offset);
206	return tag;
207}
208
209int fdt_check_node_offset_(const void *fdt, int offset)
210{
211	if ((offset < 0) || (offset % FDT_TAGSIZE)
212	    || (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE))
213		return -FDT_ERR_BADOFFSET;
214
215	return offset;
216}
217
218int fdt_check_prop_offset_(const void *fdt, int offset)
219{
220	if ((offset < 0) || (offset % FDT_TAGSIZE)
221	    || (fdt_next_tag(fdt, offset, &offset) != FDT_PROP))
222		return -FDT_ERR_BADOFFSET;
223
224	return offset;
225}
226
227int fdt_next_node(const void *fdt, int offset, int *depth)
228{
229	int nextoffset = 0;
230	uint32_t tag;
231
232	if (offset >= 0)
233		if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0)
234			return nextoffset;
235
236	do {
237		offset = nextoffset;
238		tag = fdt_next_tag(fdt, offset, &nextoffset);
239
240		switch (tag) {
241		case FDT_PROP:
242		case FDT_NOP:
243			break;
244
245		case FDT_BEGIN_NODE:
246			if (depth)
247				(*depth)++;
248			break;
249
250		case FDT_END_NODE:
251			if (depth && ((--(*depth)) < 0))
252				return nextoffset;
253			break;
254
255		case FDT_END:
256			if ((nextoffset >= 0)
257			    || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth))
258				return -FDT_ERR_NOTFOUND;
259			else
260				return nextoffset;
261		}
262	} while (tag != FDT_BEGIN_NODE);
263
264	return offset;
265}
266
267int fdt_first_subnode(const void *fdt, int offset)
268{
269	int depth = 0;
270
271	offset = fdt_next_node(fdt, offset, &depth);
272	if (offset < 0 || depth != 1)
273		return -FDT_ERR_NOTFOUND;
274
275	return offset;
276}
277
278int fdt_next_subnode(const void *fdt, int offset)
279{
280	int depth = 1;
281
282	/*
283	 * With respect to the parent, the depth of the next subnode will be
284	 * the same as the last.
285	 */
286	do {
287		offset = fdt_next_node(fdt, offset, &depth);
288		if (offset < 0 || depth < 1)
289			return -FDT_ERR_NOTFOUND;
290	} while (depth > 1);
291
292	return offset;
293}
294
295const char *fdt_find_string_(const char *strtab, int tabsize, const char *s)
296{
297	int len = strlen(s) + 1;
298	const char *last = strtab + tabsize - len;
299	const char *p;
300
301	for (p = strtab; p <= last; p++)
302		if (memcmp(p, s, len) == 0)
303			return p;
304	return NULL;
305}
306
307int fdt_move(const void *fdt, void *buf, int bufsize)
308{
309	if (fdt_chk_basic() && bufsize < 0)
310		return -FDT_ERR_NOSPACE;
311
312	FDT_RO_PROBE(fdt);
313
314	if (fdt_totalsize(fdt) > (unsigned int)bufsize)
315		return -FDT_ERR_NOSPACE;
316
317	memmove(buf, fdt, fdt_totalsize(fdt));
318	return 0;
319}
320