fix: .gguf listing walks by folder identity (sibling cycles, root links, case); dangling links not listed (attacker round 14)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-10-15 20:26:47 +03:00
co-authored by Claude Sonnet 5.5
parent 24372a5def
commit 0f86ffd909
2 changed files with 48 additions and 12 deletions
+36 -12
View File
@@ -65,6 +65,37 @@ def has_gguf_magic(path: str) -> bool:
# ── finding .gguf files ───────────────────────────────────────────────────────
def _walk(root: str, limit: int = 5000, depth: int = 12):
"""Every .gguf under `root` as a relative name, following links.
A folder is not entered again while it is one of the folders above it (same device and inode, so case, roots and
cycles through a sibling are all caught), and the walk stops after `limit` folders: a link to a whole disk is slow, not endless.
A link to a sibling folder is not a loop; it is a second name for the same files.
"""
seen = [0]
def rec(path, rel, above):
seen[0] += 1
try:
entries = sorted(os.scandir(path), key=lambda e: e.name)
except OSError:
return
for e in entries:
try:
if e.is_dir():
st = os.stat(e.path)
key = (st.st_dev, st.st_ino)
if key not in above and len(above) < depth and seen[0] < limit:
yield from rec(e.path, rel + e.name + "/", above | {key})
elif is_gguf(e.name) and e.is_file(): # is_file: a dangling link is not a model
yield rel + e.name
except OSError:
continue
st = os.stat(root)
yield from rec(root, "", frozenset({(st.st_dev, st.st_ino)}))
def gguf_names(folder: str) -> list[str]:
"""`.gguf` files under a ComfyUI model folder, relative-named like get_filename_list().
@@ -90,17 +121,10 @@ def gguf_names(folder: str) -> list[str]:
for root in roots:
if not root or not os.path.isdir(root):
continue
for dirpath, dirs, files in os.walk(root, followlinks=True):
here = os.path.realpath(dirpath) + os.sep
# a link back to this folder or one above it is a loop; a link to a sibling folder is just a second name
dirs[:] = [d for d in dirs if not here.startswith(os.path.realpath(os.path.join(dirpath, d)) + os.sep)]
for f in files:
if not is_gguf(f):
continue
rel = os.path.relpath(os.path.join(dirpath, f), root)
if rel not in seen:
seen.add(rel)
out.append(rel)
for rel in _walk(root):
if rel not in seen:
seen.add(rel)
out.append(rel)
return sorted(out)
@@ -324,7 +348,7 @@ def _load_native(path: str, pack: bool = False) -> tuple[dict, dict, str]:
# type the package knows -- do not reimplement it per quant type.
try:
arr = gguf.quants.dequantize(tensor.data, qtype)
except NotImplementedError as e:
except (NotImplementedError, ValueError) as e:
raise RuntimeError(f"{os.path.basename(path)}: tensor {name!r} uses a storage type the gguf package cannot "
f"dequantize ({e}).") from e
quantized += 1
@@ -389,3 +389,15 @@ def test_a_symlinked_folder_aliasing_a_sibling_is_listed_under_both_names(fake_f
(enc / "current").symlink_to(enc / "gemma")
names = gguf_support.gguf_names("text_encoders")
assert "current/g-Q5.gguf" in names and "gemma/g-Q5.gguf" in names
def test_cycles_through_a_sibling_folder_and_dangling_links_do_not_flood_the_listing(fake_folders):
enc = fake_folders / "text_encoders"
other = fake_folders / "other"
other.mkdir()
(enc / "p0").symlink_to(other)
(enc / "p1").symlink_to(other)
(other / "q").symlink_to(enc)
(enc / "broken.gguf").symlink_to(fake_folders / "missing.gguf")
assert gguf_support.gguf_names("text_encoders") == ["gemma-Q5_K.gguf"]
assert "broken.gguf" not in gguf_support.gguf_names("text_encoders")