Skip to content

Commit 1684f9c

Browse files
committed
harden: bound directory recursion depth when parsing an index
_xml_parse_dirtree recurses once per directory level, and the index reader sets XML_PARSE_HUGE, which removes libxml2's own nesting limit. A crafted index with deeply nested <directory> elements could recurse until the C stack overflows while mounting an untrusted cartridge. Add a depth bound (a stack-safety guard, not an LTFS format limit; the format defines no maximum depth) set well above any tree that fits in a conventional PATH_MAX, so it cannot reject a volume produced from a real filesystem. Introduces LTFS_XML_DEEP_NESTING (5051).
1 parent 7d0de7c commit 1684f9c

5 files changed

Lines changed: 25 additions & 6 deletions

File tree

messages/internal_error/root.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ root:table {
309309
I5048E:string{ "Unexpected partition map in a label." }
310310
I5049E:string{ "Unexpected blocksize in a label." }
311311
I5050E:string{ "Unexpected compression in a label." }
312+
I5051E:string{ "Directory nesting in the index is too deep." }
312313

313314
// Special error codes
314315
I9997E:string{ "Child process error (ltfsck/mkltfs): %s (%d)." }

messages/libltfs/root.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,7 @@ v
836836
17292I:string { "Current position is (%llu, %llu), Error position is (%llu, %llu)." }
837837
17293E:string { "Position mismatch. Cached tape position = %llu. Current tape position = %llu." }
838838
17294I:string { "Continue signal (%d) received" }
839+
17295E:string { "XML parser: directory nesting is too deep (limit %d)." }
839840

840841
// For Debug 19999I:string { "%s %s %d." }
841842

src/libltfs/arch/errormap.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ static struct error_map fuse_error_list[] = {
342342
{ LTFS_XML_WRONG_PART_MAP, "I5048E", EINVAL},
343343
{ LTFS_XML_WRONG_BLOCKSIZE, "I5049E", EINVAL},
344344
{ LTFS_XML_WRONG_COMP, "I5050E", EINVAL},
345+
{ LTFS_XML_DEEP_NESTING, "I5051E", EINVAL},
345346
{ EDEV_NO_SENSE, "D0000E", EIO},
346347
{ EDEV_OVERRUN, "D0002E", EIO},
347348
{ EDEV_UNDERRUN, "D0003E", ENODATA},

src/libltfs/ltfs_error.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@
317317
#define LTFS_XML_WRONG_PART_MAP 5048 /* Unexpected partition map in a label */
318318
#define LTFS_XML_WRONG_BLOCKSIZE 5049 /* Unexpected blocksize in a label */
319319
#define LTFS_XML_WRONG_COMP 5050 /* Unexpected compression in a label */
320+
#define LTFS_XML_DEEP_NESTING 5051 /* Directory nesting in the index is too deep */
320321

321322
#define LTFS_ERR_MAX 19999
322323

src/libltfs/xml_reader_libltfs.c

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,11 +1192,21 @@ static int _xml_parse_file(xmlTextReaderPtr reader, struct ltfs_index *idx, stru
11921192
* Parse a dir into the given directory.
11931193
*/
11941194

1195+
/* Guard against C-stack exhaustion while parsing the directory tree:
1196+
* _xml_parse_dirtree recurses once per nesting level and XML_PARSE_HUGE
1197+
* removes libxml2's own nesting limit, so a crafted index could otherwise
1198+
* recurse until the stack overflows. This is a stack-safety bound, not an
1199+
* LTFS format limit (the format defines no maximum depth); it is set far
1200+
* above any tree that fits in a conventional PATH_MAX, so it cannot reject
1201+
* a volume produced from a real filesystem. */
1202+
#define XML_MAX_DIRTREE_DEPTH 1024
1203+
11951204
static int _xml_parse_dirtree(xmlTextReaderPtr reader, struct dentry *parent,
11961205
struct ltfs_index *idx, struct ltfs_volume *vol,
1197-
struct name_list *dirname); /* Forward reference */
1206+
struct name_list *dirname, int depth); /* Forward reference */
11981207

1199-
static int _xml_parse_dir_contents(xmlTextReaderPtr reader, struct dentry *dir, struct ltfs_index *idx)
1208+
static int _xml_parse_dir_contents(xmlTextReaderPtr reader, struct dentry *dir,
1209+
struct ltfs_index *idx, int depth)
12001210
{
12011211
struct name_list *list = NULL, *entry_name = NULL;
12021212
CHECK_ARG_NULL(dir, -LTFS_NULL_ARG);
@@ -1228,7 +1238,7 @@ static int _xml_parse_dir_contents(xmlTextReaderPtr reader, struct dentry *dir,
12281238
ltfsmsg(LTFS_ERR, 10001E, "_xml_parse_dir_contents: dir");
12291239
return -LTFS_NO_MEMORY;
12301240
}
1231-
ret = _xml_parse_dirtree(reader, dir, idx, dir->vol, entry_name);
1241+
ret = _xml_parse_dirtree(reader, dir, idx, dir->vol, entry_name, depth + 1);
12321242
if (ret < 0) {
12331243
free(entry_name);
12341244
return ret;
@@ -1285,14 +1295,19 @@ static int _xml_parse_dir_contents(xmlTextReaderPtr reader, struct dentry *dir,
12851295
*/
12861296
static int _xml_parse_dirtree(xmlTextReaderPtr reader, struct dentry *parent,
12871297
struct ltfs_index *idx, struct ltfs_volume *vol,
1288-
struct name_list *dirname)
1298+
struct name_list *dirname, int depth)
12891299
{
12901300
unsigned long long value_int;
12911301
struct dentry *dir;
12921302

12931303
declare_parser_vars("directory");
12941304
declare_tracking_arrays(9, 1);
12951305

1306+
if (depth > XML_MAX_DIRTREE_DEPTH) {
1307+
ltfsmsg(LTFS_ERR, 17295E, XML_MAX_DIRTREE_DEPTH);
1308+
return -LTFS_XML_DEEP_NESTING;
1309+
}
1310+
12961311
if (! parent && idx->root) {
12971312
dir = idx->root;
12981313
dir->vol = vol;
@@ -1407,7 +1422,7 @@ static int _xml_parse_dirtree(xmlTextReaderPtr reader, struct dentry *parent,
14071422
check_required_tag(6);
14081423
check_empty();
14091424
if (empty == 0) {
1410-
ret = _xml_parse_dir_contents(reader, dir, idx);
1425+
ret = _xml_parse_dir_contents(reader, dir, idx, depth);
14111426
if (ret < 0)
14121427
return ret;
14131428
}
@@ -1598,7 +1613,7 @@ static int _xml_parse_schema(xmlTextReaderPtr reader, struct ltfs_index *idx, st
15981613
} else if (! strcmp(name, "directory")) {
15991614
check_required_tag(6);
16001615
assert_not_empty();
1601-
ret = _xml_parse_dirtree(reader, NULL, idx, vol, NULL);
1616+
ret = _xml_parse_dirtree(reader, NULL, idx, vol, NULL, 0);
16021617
if (ret < 0)
16031618
return ret;
16041619

0 commit comments

Comments
 (0)