Skip to content

[Bug] Heap-use-after-free in OpenColorIO::ThrowInvalidRegex when parsing malformed FileRules #2228

@oneafter

Description

@oneafter

Description

We discovered a Heap-use-after-free (UAF) vulnerability in OpenColorIO (OCIO). The crash occurs specifically when processing invalid regular expressions within the FileRules section of a YAML configuration.

The issue manifests in OpenColorIO::ThrowInvalidRegex, where the application attempts to read a string (via strlen) from a memory region that was just freed by the internal state management of std::regex.

Environment

  • OS: Linux x86_64
  • Complier: Clang with -fsanitize=address
  • Build Configure: Release
  • Affected Version: master branch

Vulnerability Details

  • Target: OpenColorIO (v2.5 dev)
  • Vulnerability Type: Heap-use-after-free (READ of size 1)
  • Severity: High (Denial of Service / Potential Memory Corruption)
  • Source File: src/OpenColorIO/FileRules.cpp
  • Function: OpenColorIO_v2_5dev::(anonymous namespace)::ThrowInvalidRegex
  • Line Number: 55
  • Root Cause Analysis:
  1. The application parses a YAML config containing a FileRule with a pattern.
  2. ValidateRegularExpression calls BuildRegularExpression (Frame #-5), which eventually invokes the std::regex compiler.
  3. During regex compilation (std::basic_regex::_M_compile), the internal NFA state vector reallocates memory (_M_realloc_insert), freeing the old buffer (as seen in the "freed by thread T0" stack trace).
  4. Crucially, when an error is detected or an exception is thrown during this process, the code enters ThrowInvalidRegex (Frame #-3).
  5. Inside ThrowInvalidRegex (Line 55), the code attempts to construct a std::string (Frame #-2) from a pointer. The ASAN report indicates this pointer points to the memory region that was just freed by the regex engine's reallocation.
  6. This suggests that ThrowInvalidRegex is accessing a dangling pointer, possibly referring to a part of the regex pattern or an internal error message string that became invalid after the vector resizing.

Reproduce

  1. Compile the ocio test harness with AddressSanitizer enabled (-fsanitize=address -g)
  2. Run the harness with the attached repro input:
./harness < repro

ASAN report

==13647==ERROR: AddressSanitizer: heap-use-after-free on address 0x5080000011fc at pc 0x55874bd813c7 bp 0x7ffde69ad190 sp 0x7ffde69ac958
READ of size 1 at 0x5080000011fc thread T0
    #0 0x55874bd813c6 in strlen (/src/OpenColorIO/build/harness_ocio+0x453c6) (BuildId: 83a4ad1aabb1626c20201cde350ca93bff741638)
    #1 0x7fed531598ca in std::char_traits<char>::length(char const*) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/char_traits.h:399:9
    #2 0x7fed531598ca in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::basic_string<std::allocator<char>>(char const*, std::allocator<char> const&) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/basic_string.h:648:30
    #3 0x7fed53a62f08 in OpenColorIO_v2_5dev::(anonymous namespace)::ThrowInvalidRegex(char const*, char const*) /src/OpenColorIO/src/OpenColorIO/FileRules.cpp:55:26
    ...
    #10 0x7fed53b8ba69 in OpenColorIO_v2_5dev::(anonymous namespace)::load(YAML::Node const&, std::shared_ptr<OpenColorIO_v2_5dev::Config>&, char const*) /src/OpenColorIO/src/OpenColorIO/OCIOYaml.cpp:4605:21
    #11 0x7fed53b7de5d in OpenColorIO_v2_5dev::OCIOYaml::Read(std::istream&, std::shared_ptr<OpenColorIO_v2_5dev::Config>&, char const*) /src/OpenColorIO/src/OpenColorIO/OCIOYaml.cpp:5418:9
    #12 0x7fed53454034 in OpenColorIO_v2_5dev::Config::Impl::Read(std::istream&, char const*) /src/OpenColorIO/src/OpenColorIO/Config.cpp:5543:5
    #13 0x7fed5344cfa6 in OpenColorIO_v2_5dev::Config::CreateFromStream(std::istream&) /src/OpenColorIO/src/OpenColorIO/Config.cpp:1211:12
    #14 0x55874be4d405 in main /src/OpenColorIO/harness_ocio.cpp:33:49
    #15 0x7fed529da1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #16 0x7fed529da28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #17 0x55874bd694d4 in _start (/src/OpenColorIO/build/harness_ocio+0x2d4d4) (BuildId: 83a4ad1aabb1626c20201cde350ca93bff741638)

0x5080000011fc is located 92 bytes inside of 96-byte region [0x5080000011a0,0x508000001200)
freed by thread T0 here:
    #0 0x55874be4b606 in operator delete(void*, unsigned long) (/src/OpenColorIO/build/harness_ocio+0x10f606) (BuildId: 83a4ad1aabb1626c20201cde350ca93bff741638)
    #1 0x7fed5338e6e8 in std::__new_allocator<std::__detail::_State<char>>::deallocate(std::__detail::_State<char>*, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/new_allocator.h:172:2
    #2 0x7fed5338e6e8 in std::allocator_traits<std::allocator<std::__detail::_State<char>>>::deallocate(std::allocator<std::__detail::_State<char>>&, std::__detail::_State<char>*, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/alloc_traits.h:517:13
    #3 0x7fed5338e6e8 in std::_Vector_base<std::__detail::_State<char>, std::allocator<std::__detail::_State<char>>>::_M_deallocate(std::__detail::_State<char>*, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:390:4
    #4 0x7fed5338e6e8 in void std::vector<std::__detail::_State<char>, std::allocator<std::__detail::_State<char>>>::_M_realloc_insert<std::__detail::_State<char>>(__gnu_cxx::__normal_iterator<std::__detail::_State<char>*, std::vector<std::__detail::_State<char>, std::allocator<std::__detail::_State<char>>>>, std::__detail::_State<char>&&) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/vector.tcc:519:7
    #5 0x7fed5338cd75 in std::__detail::_State<char>& std::vector<std::__detail::_State<char>, std::allocator<std::__detail::_State<char>>>::emplace_back<std::__detail::_State<char>>(std::__detail::_State<char>&&) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/vector.tcc:123:4
    #6 0x7fed5338cd75 in std::vector<std::__detail::_State<char>, std::allocator<std::__detail::_State<char>>>::push_back(std::__detail::_State<char>&&) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:1299:9
    #7 0x7fed5338cd75 in std::__detail::_NFA<std::__cxx11::regex_traits<char>>::_M_insert_state(std::__detail::_State<char>) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/regex_automaton.h:328:8
    ...
    #26 0x7fed53b8ba69 in OpenColorIO_v2_5dev::(anonymous namespace)::load(YAML::Node const&, std::shared_ptr<OpenColorIO_v2_5dev::FileRules>&, bool&) /src/OpenColorIO/src/OpenColorIO/OCIOYaml.cpp:4180:21
    #27 0x7fed53b8ba69 in OpenColorIO_v2_5dev::(anonymous namespace)::load(YAML::Node const&, std::shared_ptr<OpenColorIO_v2_5dev::Config>&, char const*) /src/OpenColorIO/src/OpenColorIO/OCIOYaml.cpp:4605:21
    #28 0x7fed53b7de5d in OpenColorIO_v2_5dev::OCIOYaml::Read(std::istream&, std::shared_ptr<OpenColorIO_v2_5dev::Config>&, char const*) /src/OpenColorIO/src/OpenColorIO/OCIOYaml.cpp:5418:9
    #29 0x7fed53454034 in OpenColorIO_v2_5dev::Config::Impl::Read(std::istream&, char const*) /src/OpenColorIO/src/OpenColorIO/Config.cpp:5543:5
    #30 0x7fed5344cfa6 in OpenColorIO_v2_5dev::Config::CreateFromStream(std::istream&) /src/OpenColorIO/src/OpenColorIO/Config.cpp:1211:12
    #31 0x55874be4d405 in main /src/OpenColorIO/harness_ocio.cpp:33:49
    #32 0x7fed529da1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #33 0x7fed529da28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #34 0x55874bd694d4 in _start (/src/OpenColorIO/build/harness_ocio+0x2d4d4) (BuildId: 83a4ad1aabb1626c20201cde350ca93bff741638)

previously allocated by thread T0 here:
    #0 0x55874be4a981 in operator new(unsigned long) (/src/OpenColorIO/build/harness_ocio+0x10e981) (BuildId: 83a4ad1aabb1626c20201cde350ca93bff741638)
    #1 0x7fed5338db6c in std::__new_allocator<std::__detail::_State<char>>::allocate(unsigned long, void const*) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/new_allocator.h:151:27
    #2 0x7fed5338db6c in std::allocator_traits<std::allocator<std::__detail::_State<char>>>::allocate(std::allocator<std::__detail::_State<char>>&, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/alloc_traits.h:482:20
    #3 0x7fed5338db6c in std::_Vector_base<std::__detail::_State<char>, std::allocator<std::__detail::_State<char>>>::_M_allocate(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:381:20
    ...
    #22 0x7fed53b8ba69 in OpenColorIO_v2_5dev::(anonymous namespace)::load(YAML::Node const&, std::shared_ptr<OpenColorIO_v2_5dev::Config>&, char const*) /src/OpenColorIO/src/OpenColorIO/OCIOYaml.cpp:4605:21
    #23 0x7fed53b7de5d in OpenColorIO_v2_5dev::OCIOYaml::Read(std::istream&, std::shared_ptr<OpenColorIO_v2_5dev::Config>&, char const*) /src/OpenColorIO/src/OpenColorIO/OCIOYaml.cpp:5418:9
    #24 0x7fed53454034 in OpenColorIO_v2_5dev::Config::Impl::Read(std::istream&, char const*) /src/OpenColorIO/src/OpenColorIO/Config.cpp:5543:5
    #25 0x7fed5344cfa6 in OpenColorIO_v2_5dev::Config::CreateFromStream(std::istream&) /src/OpenColorIO/src/OpenColorIO/Config.cpp:1211:12
    #26 0x55874be4d405 in main /src/OpenColorIO/harness_ocio.cpp:33:49
    #27 0x7fed529da1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #28 0x7fed529da28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #29 0x55874bd694d4 in _start (/src/OpenColorIO/build/harness_ocio+0x2d4d4) (BuildId: 83a4ad1aabb1626c20201cde350ca93bff741638)

SUMMARY: AddressSanitizer: heap-use-after-free (/src/OpenColorIO/build/harness_ocio+0x453c6) (BuildId: 83a4ad1aabb1626c20201cde350ca93bff741638) in strlen
Shadow bytes around the buggy address:
  0x508000000f00: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
  0x508000000f80: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
  0x508000001000: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
  0x508000001080: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 05 fa
  0x508000001100: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 05 fa
=>0x508000001180: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd[fd]
  0x508000001200: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
  0x508000001280: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
  0x508000001300: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 05 fa
  0x508000001380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x508000001400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==13647==ABORTING

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions