Daemon icon indicating copy to clipboard operation
Daemon copied to clipboard

Weird `munmap_chunk(): invalid pointer` crash when freeing `pic` in `R_FindImageFile()`

Open illwieckz opened this issue 1 year ago • 4 comments

I noticed a very weird munmap_chunk(): invalid pointer crash when freeing pic in R_FindImageFile():

https://github.com/DaemonEngine/Daemon/blob/4cfabd48eda6585ee2e617a3e9377eb8330c3cc0/src/engine/renderer/tr_image.cpp#L1863

I only reproduce it when testing on a very low end Raspberry PI 3. For example I don't reproduce it on my amd64 workstation, and I don't reproduce it on an orangepi5 board which is also arm64 like the rpi3, but more powerful.

At first I thought this could be some memory being missing, or other problems being caused by the fact such hardware is very low end.

But then I noticed the crash happened when freeing something. This is not caused by using a very old OpenGL. This is even not caused by the memory being to small.

The things is that if I comment out the Z_Free( *pic ); line, the bug is gone and the game runs, while actually consuming more memory!

I then suspected that we may have some memory corruption somewhere, and that under the specific conditions of that hardware it actually breaks something. But I always get that error when loading the gfx/weapons/flamer/haze_n image, not on other images loaded before it. This looks very reproducible for a memory corruption, I always reproduce it, and always at the same place whatever the build I use (Debug, MinSizeRel, running on debugger or not, using different HunkMeg sizes…).

So, I don't know what is happening. But Z_Free() which just calls standard free() is falling here.

I've checked it with some printf(), but the pointer is never null when it fails.

Also, pic is passed as const on functions called prior the Z_Free() call, so it is very unlikely that the pointer get modified because of a bug.

Also, the pointer address never changes once set, I've checked with some printf().

I remember it worked before 0.55.2 was released (I have not yet tested the 0.55.2 release).

Debug: Found CRN image candidate 'gfx/weapons/flamer/smoke': gfx/weapons/flamer/smoke.crn 
Debug: Found 256×256 CRN image 'gfx/weapons/flamer/smoke': gfx/weapons/flamer/smoke.crn 
Debug: Creating image gfx/weapons/flamer/smoke (256×256, 9 mips) 
Debug: Allocating image gfx/weapons/flamer/smoke 
Debug: Uploading image gfx/weapons/flamer/smoke (128×128, 1 layers, 0xde1 type, 0x83f3 format) 
Debug: Found CRN image candidate 'gfx/weapons/flamer/haze_n': gfx/weapons/flamer/haze_n.crn 
Debug: Found 128×128 CRN image 'gfx/weapons/flamer/haze_n': gfx/weapons/flamer/haze_n.crn 
Debug: Creating image gfx/weapons/flamer/haze_n (128×128, 8 mips) 
Debug: Allocating image gfx/weapons/flamer/haze_n 
Debug: Uploading image gfx/weapons/flamer/haze_n (128×128, 1 layers, 0xde1 type, 0x83f3 format) 
]munmap_chunk(): invalid pointer

Thread 1 "daemon" received signal SIGABRT, Aborted.
0x0000fffff71d09f0 in ?? () from /lib/aarch64-linux-gnu/libc.so.6
(gdb) bt
#0  0x0000fffff71d09f0 in ?? () from /lib/aarch64-linux-gnu/libc.so.6
#1  0x0000fffff718a72c in raise () from /lib/aarch64-linux-gnu/libc.so.6
#2  0x0000fffff717747c in abort () from /lib/aarch64-linux-gnu/libc.so.6
#3  0x0000fffff71c4aac in ?? () from /lib/aarch64-linux-gnu/libc.so.6
#4  0x0000fffff71dae4c in ?? () from /lib/aarch64-linux-gnu/libc.so.6
#5  0x0000fffff71db04c in ?? () from /lib/aarch64-linux-gnu/libc.so.6
#6  0x0000fffff71df6a4 in free () from /lib/aarch64-linux-gnu/libc.so.6
#7  0x0000aaaaaabc049c in Z_Free (ptr=<optimized out>, ptr=<optimized out>)
    at /src/engine/qcommon/qcommon.h:582
#8  R_FindImageFile (imageName=<optimized out>, imageParams=...)
    at /src/engine/renderer/tr_image.cpp:1863
#9  0x0000aaaaaabea2d0 in LoadMap (stage=stage@entry=0xaaaaabbe0d38 <_ZL6stages.lto_priv.0>, buffer=<optimized out>, 
    buffer@entry=0xfffffffece70 "gfx/weapons/flamer/haze_n", type=<optimized out>, bundleIndex=bundleIndex@entry=0)
    at /src/engine/renderer/tr_shader.cpp:1534
#10 0x0000aaaaaabecc30 in ParseStage (stage=<optimized out>, text=0xfffffffed730)
    at /src/engine/renderer/tr_shader.cpp:3284
#11 0x0000aaaaaabf0898 in ParseShader (_text=<optimized out>)
    at /src/engine/renderer/tr_shader.cpp:3862
#12 R_FindShader (name=<optimized out>, name@entry=0xaaaaad910bd0 "gfx/weapons/flamer/haze", 
    type=type@entry=shaderType_t::SHADER_2D, flags=flags@entry=64)
    at /src/engine/renderer/tr_shader.cpp:6248
#13 0x0000aaaaaabf2a48 in RE_RegisterShader (name=0xaaaaad910bd0 "gfx/weapons/flamer/haze", flags=64)
    at /src/engine/renderer/tr_shader.cpp:6455
#14 0x0000aaaaaac867d8 in operator() (handle=@0xfffffffedc28: 0, flags=<optimized out>, 
    name="gfx/weapons/flamer/haze", __closure=<optimized out>)
    at /src/engine/client/cl_cgame.cpp:1262
#15 Util::apply_impl<CGameVM::QVMSyscall(int, Util::Reader&, IPC::Channel&)::<lambda(const std::string&, int, int&)>, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&, int&&, int&>, 0, 1, 2> (
    func=..., tuple=...) at /src/common/Util.h:136
#16 Util::apply<CGameVM::QVMSyscall(int, Util::Reader&, IPC::Channel&)::<lambda(const std::string&, int, int&)>, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&, int&&, int&> > (tuple=..., 
    func=...) at /src/common/Util.h:141
#17 IPC::detail::HandleMsg<CGameVM::QVMSyscall(int, Util::Reader&, IPC::Channel&)::<lambda(const std::string&, int, int&)>, IPC::Message<IPC::Id<0, 37>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, IPC::Reply<int> > (func=..., reader=..., channel=...)
    at /src/common/IPC/Channel.h:217
#18 IPC::HandleMsg<IPC::SyncMessage<IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)37>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, IPC::Reply<int> >, CGameVM::QVMSyscall(int, Util::Reader&, IPC::Channel&)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int, int&)#22}>(IPC::Channel&, Util::Reader, CGameVM::QVMSyscall(int, Util::Reader&, IPC::Channel&)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int, int&)#22}&&) [clone .isra.0] (
    channel=..., reader=..., func=...)
    at /src/common/IPC/Channel.h:241
#19 0x0000aaaaaac67ba8 in CGameVM::QVMSyscall(int, Util::Reader&, IPC::Channel&) [clone .constprop.0] (
    syscallNum=<optimized out>, reader=..., channel=..., this=<optimized out>)
    at /src/engine/client/cl_cgame.cpp:1261
#20 0x0000aaaaaab22f84 in CGameVM::Syscall (this=0xaaaaaad61748 <cgvm>, id=<optimized out>, reader=..., channel=...)
    at /src/engine/client/cl_cgame.cpp:1066
#21 0x0000aaaaaac777d8 in VM::VMBase::SendMsg<IPC::SyncMessage<IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)0>, int>, IPC::Reply<> >, int>(int&&)::{lambda(unsigned int, Util::Reader)#1}::operator()(unsigned int, Util::Reader) (
    reader=..., id=37, __closure=0xfffffffee430)
    at /src/engine/framework/VirtualMachine.h:138
#22 IPC::detail::SendMsg<VM::VMBase::SendMsg<IPC::SyncMessage<IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)1>, int, int, glconfig_t, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul> >, IPC::Reply<> >, int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&>(int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&)::{lambda(unsigned int, Util::Reader)#1}&, IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)1>, int, int, glconfig_t, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul> >, IPC::Reply<>, int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&>(IPC::Channel&, VM::VMBase::SendMsg<IPC::SyncMessage<IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)1>, int, int, glconfig_t, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul> >, IPC::Reply<> >, int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&>(int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&)::{lambda(unsigned int, Util::Reader)#1}&, IPC::SyncMessage<IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)1>, int, int, glconfig_t, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul> >, IPC::Reply<> >, int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&) [clone .constprop.0]
    (messageHandler=..., channel=...)
    at /src/common/IPC/Channel.h:174
#23 0x0000aaaaaab1a784 in IPC::SendMsg<IPC::SyncMessage<IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)1>, int, int, glconfig_t, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul> >, IPC::Reply<> >, VM::VMBase::SendMsg<IPC::SyncMessage<IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)1>, int, int, glconfig_t, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul> >, IPC::Reply<> >, int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&>(int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&)::{lambda(unsigned int, Util::Reader)#1}, int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&>(IPC::Channel&, VM::VMBase::SendMsg<IPC::SyncMessage<IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)1>, int, int, glconfig_t, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul> >, IPC::Reply<> >, int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&>(int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&)::{lambda(unsigned int, Util::Reader)#1}&&, int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&) (messageHandler=..., channel=...)
    at /src/common/IPC/Channel.h:234
#24 VM::VMBase::SendMsg<IPC::SyncMessage<IPC::Message<IPC::Id<(unsigned short)0, (unsigned short)1>, int, int, glconfig_t, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul> >, IPC::Reply<> >, int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&>(int&, int&, glconfig_t&, std::array<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 1024ul>&) (this=<optimized out>)
    at /src/engine/framework/VirtualMachine.h:136
#25 CGameVM::CGameInit (this=<optimized out>, clientNum=<optimized out>, serverMessageNum=8)
    at /src/engine/client/cl_cgame.cpp:977
#26 CL_InitCGame () at /src/engine/client/cl_cgame.cpp:628
#27 0x0000aaaaaab22c78 in CL_DownloadsComplete ()
    at /src/engine/client/cl_download.cpp:109
#28 0x0000aaaaaab3616c in CL_InitDownloads ()
    at /src/engine/client/cl_download.cpp:237
#29 CL_ParseGamestate (msg=msg@entry=0xfffffffeea30)
    at /src/engine/client/cl_parse.cpp:460
#30 0x0000aaaaaab368cc in CL_ParseServerMessage (msg=0xfffffffeea30)
    at /src/engine/client/cl_parse.cpp:570
#31 0x0000aaaaaab2ec9c in CL_PacketEvent (from=..., msg=0xfffffffeea30)
    at /src/engine/client/cl_main.cpp:1946
#32 0x0000aaaaaaaf183c in HandlePacketEvent (event=...)
    at /src/engine/qcommon/common.cpp:393
#33 Com_EventLoop () at /src/engine/qcommon/common.cpp:512
#34 0x0000aaaaaaaf22a8 in Com_Frame ()
    at /src/engine/qcommon/common.cpp:879
#35 0x0000aaaaaaaf4cd4 in Application::ClientApplication::Frame (this=0xaaaaaad605a8 <Application::GetApp()::app>)
    at /src/engine/client/ClientApplication.cpp:110
#36 0x0000aaaaaaad8a18 in Application::Frame ()
    at /src/engine/framework/Application.cpp:87
#37 main (argc=<optimized out>, argv=<optimized out>)
    at /src/engine/framework/System.cpp:1006

illwieckz avatar Mar 13 '25 22:03 illwieckz

I've noticed before that some of that code dealing with image arrays is really bad. It's like no one is really keeping track of the number of images in the array; some functions seem to have no limit of images they might add although the array is of fixed size; etc. It would be unsurprising if it does not handle errors correctly.

slipher avatar Mar 14 '25 03:03 slipher

I've noticed before that some of that code dealing with image arrays is really bad. It's like no one is really keeping track of the number of images in the array; some functions seem to have no limit of images they might add although the array is of fixed size; etc. It would be unsurprising if it does not handle errors correctly.

Related: #271.

VReaperV avatar Mar 14 '25 04:03 VReaperV

I also reproduce it on some low-end old Intel cards.

illwieckz avatar Aug 08 '25 22:08 illwieckz

I dreamt about it… 🤪 and maybe it's related to image sizes. Such normal maps may use some noPicMip keywords.

I noticed the Intel card reproducing the bug also displays garbage on the main menu background when the game starts on a screen having a 1920×1080 size, but when I resize the window, the game renders correctly. We switched the UI images from nopPicMip to the fitScreen feature instead, so reducing the window forces the downsize of the UI images while possible noPicMip normal maps may be uploaded as is and maybe get a broken pointer in return or something like that.

Or maybe some memory get allocated with the size of the actual limit of the hardware but we asked for a larger size and for some reasons there is no allocation error and then we attempt to free more than what we have.

All in all if I get a garbage main menu background on a large screen it means we don't check properly for the image limits.

I had this branch to test the image upload against the OpenGL driver using fake GL_PROXY allocations:

  • https://github.com/DaemonEngine/Daemon/pull/1131

Maybe that can fix it once for all?

illwieckz avatar Aug 09 '25 13:08 illwieckz