bug: a malformed filename throws std::regex_error out of ImageCache, and catching it deadlocks the file cache
Los mantenedores suelen responder en 2 días
Evaluación
Este issue todavía no se ha evaluado.
Descripción
Describe the bug
ImageCacheFile::udim_setup builds a tile-matching regex from the caller's
filename and compiles it unguarded, at src/libtexture/imagecache.cpp:4179:
std::regex decoder(pat);
There is no try/catch. There is none anywhere in the file:
grep -c catch src/libtexture/imagecache.cpp returns 0 at main.
pat is built at :4169 by ImageCacheFile::udim_to_wildcard, which calls
Filesystem::filename_to_regex and then substitutes the udim specifiers.
filename_to_regex (src/libutil/filesystem.cpp:1148-1163) replaces . with
\. unconditionally, escapes ( ) [ ] { } when any of them is present, and,
because simple_glob defaults to true (filesystem.h:420), rewrites ? to
.? and * to .*. It does not touch +. So a + with nothing repeatable
before it survives into the pattern and reaches std::regex as a quantifier
with no operand:
"+<UDIM>.exr" -> filename_to_regex -> "+<UDIM>\.exr"
-> <UDIM> substitution -> "+([0-9]{4})\.exr"
-> std::regex -> throws std::regex_error
The exception type is std::regex_error on any implementation, but the code
and message are not. On this pattern MSVC 1951 reports error_badrepeat,
"One of *?+{ was not preceded by a valid regular expression", while
libstdc++ 13.1 reports error_paren, "Mismatched '(' and ')' in regular
expression". libstdc++ says the same for a bare +abc, so that is a
difference in how each library describes a leading quantifier rather than
anything about this pattern. The traces below are from the MSVC build.
A leading + is the whole trigger as far as I can find. Sweeping the regex
metacharacters through get_image_info, one fresh name per query:
+<UDIM>.exr THREW regex_error(error_badrepeat)
++<UDIM>.exr THREW regex_error(error_badrepeat)
*<UDIM>.exr returned false, no throw
?<UDIM>.exr returned false, no throw
|<UDIM>.exr returned false, no throw
^<UDIM>.exr returned false, no throw
$<UDIM>.exr returned false, no throw
(<UDIM>.exr returned false, no throw
[<UDIM>.exr returned false, no throw
{<UDIM>.exr returned false, no throw
tex+<UDIM>.exr returned false, no throw
tex<UDIM>+.exr returned false, no throw
( [ { are escaped, * and ? are translated, | ^ $ are already valid in
that position, and a + with a character before it is a well formed
quantifier. Each name is queried once only, because a name that throws leaves
its own cache bin locked and a retry blocks, which is the second half of this
report.
The exception unwinds out of the ImageCacheFile constructor, through
find_file, and out of whichever public API first touched the name. Measured
on ImageCache::get_image_info, whose documented error contract is a false
return with the reason available from geterror():
calling get_image_info on the malformed pattern...
ESCAPED as std::exception: regex_error(error_badrepeat): One of *?+{ was not
preceded by a valid regular expression.
oiiotool dies on it
oiiotool --info '+<UDIM>.exr' prints nothing and terminates with
0xC0000409, a fail fast exit rather than a return. Measured on main and on
3.2.0.2dev:
> oiiotool --info "+<UDIM>.exr"
(no output at all)
exit code -1073740791, that is 0xC0000409
For contrast, a file that is merely missing is reported properly and exits 1:
> oiiotool --info nosuchfile.exr
oiiotool ERROR: read : File does not exist: "nosuchfile.exr"
exit code 1
So this is reachable from the command line, as a crash with no diagnostic, and
not only from a library caller.
And catching it is not a recovery
The throwing constructor runs between find_file's manual lock pair,
src/libtexture/imagecache.cpp:1563 and :1574, with the new ImageCacheFile(...) at :1570 between them:
size_t bin = m_files.lock_bin(filename);
...
m_files.unlock_bin(bin);
There is no RAII between them, so the unwinding exception leaves the
file-cache bin locked. Measured, in one process, on the same ImageCache:
1. malformed pattern, catching the throw...
caught: regex_error(error_badrepeat): One of *?+{ was not preceded by a
valid regular expression.
2. second query, a normal readable file...
returned true, wrote 1
3. third query, the SAME malformed name again...
<hangs forever; killed after 25 seconds>
Step 2 is the useful part of that trace: the one unrelated file I tried still
works, so the damage is one bin rather than the whole map. That is narrower
than it sounds. FILE_CACHE_SHARDS is 64 (imagecache_pvt.h:35), so roughly
one other filename in 64 hashes into the poisoned bin and blocks the same way,
and anything that walks the whole file map blocks on it too. Step 3 is the
unrecoverable part. A caller that catches the exception and retries the same
name, which is the natural thing to do when an error looks transient, blocks
permanently with no diagnostic.
The project already guards the same compilation elsewhere
Filesystem::get_directory_entries compiles the same kind of
caller-derived pattern inside try/catch, filesystem.cpp:279-298:
std::regex re;
try {
re = std::regex(filter_regex);
...
} catch (...) {
udim_setup passes pat to get_directory_entries at :4170 to list
candidate tiles, then hands the same string to a bare std::regex at :4179.
So the identical pattern is compiled once inside a try and once without one,
nine lines apart.
OpenImageIO version and dependencies
Measured on a source build of main at 0558643e7:
OIIO 3.3.0.0dev | Windows/x86_64
Build compiler: MSVS 1951 | C++17/199711
Dependencies: OpenEXR 3.4.7, Imath 3.2.2, fmt 12.1.0, libdeflate 1.25, ZLIB 1.3.1
and originally found on a local 3.2.2.0 release build:
OIIO 3.2.2.0 | Windows/x86_64
Build compiler: MSVS 1951 | C++17/199711
HW features enabled at build: sse2,sse3,ssse3,sse41,sse42
No CUDA support (disabled / unavailable at build time)
Dependencies: BZip2 1.0.8, DCMTK NONE, FFmpeg NONE, fmt 12.1.0, Freetype 2.13.3, GIF 5.2.2, Imath 3.2.2, JXL NONE, libdeflate 1.25, Libheif NONE, libjpeg-turbo 3.1.3, LibRaw NONE, libuhdr NONE, OpenColorIO 2.5.1, OpenCV NONE, OpenEXR 3.4.7, OpenJPEG 2.5.4, openjph 0.26.3, PNG 1.6.55, Ptex NONE, Ptex NONE, Robinmap 1.4.1, TBB NONE, TIFF 4.7.1, WebP 1.6.0, ZLIB 1.3.1, zstd 1.5.7
Nothing unusual about either environment and no nonstandard build options. The
cited lines are at main. imagecache.cpp is byte identical at main and
v3.2.2.0, so every line number above applies unchanged there. On the 3.1.x
line the same unguarded construction is at :4159 and the find_file lock
pair is at :1542 and :1553, checked at v3.1.9.0, v3.1.12.0 and
v3.1.19.0.
To Reproduce
No image files are needed. The name does not have to exist on disk; the
exception is thrown while building the pattern, before any tile is opened.
#include <OpenImageIO/imagecache.h>
#include <cstdio>
#include <exception>
int main()
{
auto ic = OIIO::ImageCache::create(false);
int n = -1;
std::printf("1. malformed pattern\n"); std::fflush(stdout);
try {
ic->get_image_info(OIIO::ustring("+<UDIM>.exr"), 0, 0,
OIIO::ustring("subimages"), OIIO::TypeInt, &n);
std::printf(" returned without throwing\n");
} catch (const std::exception& e) {
std::printf(" threw: %s\n", e.what());
}
std::fflush(stdout);
std::printf("2. same name again, after catching\n"); std::fflush(stdout);
try {
ic->get_image_info(OIIO::ustring("+<UDIM>.exr"), 0, 0,
OIIO::ustring("subimages"), OIIO::TypeInt, &n);
} catch (const std::exception& e) {
std::printf(" threw: %s\n", e.what());
}
std::printf("reached the end\n");
return 0;
}
Expected: both calls return false with the reason in geterror(), and the
program reaches the end.
Observed: step 1 throws std::regex_error, and step 2 never
returns. Run it under a timeout. Linking needs OpenImageIO and
OpenImageIO_Util.
The fix
Two independent halves.
Guard the compilation, so a filename that is not valid regex marks the file
broken with a clear message instead of throwing:
std::regex decoder;
try {
decoder = std::regex(pat);
} catch (const std::regex_error& e) {
mark_broken(Strutil::fmt::format(
"\"{}\" is not a usable UDIM pattern: {}", m_filename, e.what()));
return;
}
And make the bin lock in find_file an RAII guard, which closes the deadlock
half for this throw and for any future one on that path. The second change is
worth making on its own merits: there is currently no exception-safe path
between :1563 and :1574.
Test coverage
testsuite/texture-udim covers well-formed patterns only. A regression test
would be a single get_image_info on a name beginning with +, asserting a
false return rather than an exception, with a second call on the same name
asserting it also returns rather than blocking.
- Lenguaje dominante
- C++
- Estrellas
- 2.4k
- Forks
- 718
- Merge medio
- 2 d 18 h
- PR fusionados (30 d)
- 85
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de AcademySoftwareFoundation/OpenImageIO
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
AcademySoftwareFoundation/OpenImageIO#5662 ·
Los mantenedores suelen responder en 2 días
-
bug: copy() writes destination channels the source does not have, taking the values from past the source's pixelPosiblemente ocupada @lgritz la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 35/100
AcademySoftwareFoundation/OpenImageIO#5661 ·
Los mantenedores suelen responder en 2 días
-
PSD: layers with a "real user supplied layer mask" (channel -3) stored as Raw fail to load ("unsupported compression")Posiblemente ocupada @linsen458-spec la tomó hace 1 día. Abierto
Dificultad 3/5 1-2 días Aptitud para principiantes 72/100
AcademySoftwareFoundation/OpenImageIO#5659 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
bug: deep paste wraps into the next scanline instead of clipping, and ignores chbeginPosiblemente ocupada @lgritz la tomó hace 2 días. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
AcademySoftwareFoundation/OpenImageIO#5649 ·
Los mantenedores suelen responder en 2 días
-
bug: make_kernel("binomial", ...) indexes its tap arrays past their allocation for even or fractional extentsPosiblemente ocupada @lgritz la tomó hace 2 días. Abierto
Dificultad 3/5 Medio día Aptitud para principiantes 62/100
AcademySoftwareFoundation/OpenImageIO#5648 ·
Los mantenedores suelen responder en 2 días
Todos los issues de AcademySoftwareFoundation/OpenImageIO
Issues similares
-
documentation enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
QuantStack/git2cpp#203 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
BaptisteHudyma/Lamp-Da#588 ·
Los mantenedores suelen responder en 1 día
-
bug macOS networking
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
docling-project/docling-parse#448 ·
Los mantenedores suelen responder en 5 días