Bug 192839

Summary: Duplicate entries in an mtree file cause nmtree to coredump
Product: Base System Reporter: Enji Cooper <ngie>
Component: binAssignee: freebsd-bugs (Nobody) <bugs>
Status: New ---    
Severity: Affects Some People CC: bdrewery, brooks, emaste, felix.dietrich+freebsd-bugtracker, lwhsu, ngie, pat
Priority: ---    
Version: CURRENT   
Hardware: Any   
OS: Any   

Description Enji Cooper freebsd_committer freebsd_triage 2014-08-19 20:49:57 UTC
% uname -a
FreeBSD freebsd-11-x64.localdomain 11.0-CURRENT FreeBSD 11.0-CURRENT #10 r269317+78cba4b(isilon-atf): Wed Aug 13 21:46:54 PDT 2014     root@freebsd-11-x64.localdomain:/usr/obj/usr/src/sys/GENERIC-DEBUG  amd64
% /bin/sh
% cat | nmtree -dEU <<EOF
.
    duplicate_entry
    ..
    duplicate_entry
    ..
..
EOF
^D
Bus error (core dumped)
%
Comment 1 Enji Cooper freebsd_committer freebsd_triage 2014-08-20 04:27:35 UTC
An even simpler repro is:

% /bin/sh
% nmtree -dEU <<EOF
.
    duplicate_entry
    ..
    duplicate_entry
    ..
..
EOF
Bus error: 10 (core dumped)
%
Comment 2 Enji Cooper freebsd_committer freebsd_triage 2015-10-23 09:00:39 UTC
*sigh*. This impacts makefs too, making makefs -D useless.
Comment 3 Enji Cooper freebsd_committer freebsd_triage 2015-10-31 06:04:06 UTC
Another case that repros this is as follows (not sure if this mtree file is valid though):

.
    a
    ..
..
.
    a
    ..
..
Comment 4 Enji Cooper freebsd_committer freebsd_triage 2017-11-05 20:59:18 UTC
Handing a number of makefs, mtree, and msdosfs bugs in my queue over to emaste@.
Comment 5 Ed Maste freebsd_committer freebsd_triage 2017-11-21 01:42:53 UTC
I do not expect to be able to look at mtree bugs in the near term.
Comment 6 Felix Dietrich 2023-07-23 15:29:35 UTC
I am unable to reproduce the core dump with the provided examples:

The example of comment #1 yields (the “-E” to exclude a tag should presumably have been a “-e” to not complain about files not in the specification) :

  nmtree: no parent node
  nmtree: failed at line 6 of the specification

For the example in comment #3 only the line number in the output differs:

  nmtree: no parent node
  nmtree: failed at line 4 of the specification

A slight modification to the example found in comment #1 (specifying the default type to be “dir”)

  /set type=dir
  .
      duplicate_entry
      ..
      duplicate_entry
      ..
  ..

yields, on an empty root directory, the following:

  ./dup missing (directory not created: user not specified)
Comment 7 Felix Dietrich 2023-07-23 15:30:05 UTC
The problem that brought me here is a segmentation fault resulting from feeding the following example to nmtree:

  /set type=dir
  .
    dup
    ..
    dup
      child_entry type=file


Line numbers in the following paragraph are according to FreeBSD 12.4-RELEASE-p2 (git hash 149768b65d619833331bcf0c2fb121b20643f2f1).

The segmentation fault is caused by using the memory pointed to by centry (“last = cenry” in the “spec” function [spec.c:252]) after it has been freed in replacenode (“free(new)” [spec.c:536]).  (A comment in the addchild function [spec.c:775] indicates that at least addchild is aware that replacenode will free the centry.)  The memory gets reused for the next centry (“centry = calloc(…” [spec.c:207]) and assigned the global defaults (“*centry = ginfo” [spec.c:209]).  The parent member of the ginfo record is NULL [spec.c:122], and, as a consequence of the faulty memory reuse, addchild gets passed NULL (“addchild(last->parent, centry)” [spec.c:260]) for its pathparent parameter.  This results in a segmentation fault early in addchild because of a NULL dereference (“cur = pathparent->child” [spec.c:734]).  Also note that last->type gets overridden and set to F_FILE [spec.c:215], and, therefore, not the branch “last->type == F_DIR && !(last->flags & F_DONE)” [spec.c:245] is taken, as one would expect by correct operation, but the final else branch “new relative child in parent dir” [spec.c:253].

I have not reasoned much about solutions.  It might be possible for a quick and dirty fix to dispense with the “free(new)” call in replacenode [spec.c:536] at the cost of leaking memory and making the code even harder to reason about.  Another solutions could make replacenode actually replace the node (removing “cur” from the data structure and emplacing “new” in its stead) instead of overwriting the members of the existing “cur” node – but I have not tried to understand and follow the assumptions addchild makes of replacenodeʼs operations.