ASAN reports invalid read in Chaos' FVelocityAndPressureField::Apply

We run a game with ASAN in different scenario and found an asan crash in Chaos (heap-buffer-overflow). It crashed on a getting a wrong data from `InParticles` because it took wrong index from `FVelocityAndPressureField::PointToTriangleMap` which is array view to an array from `FTriangleMesh`.

Inspecting that data it seems that the array was reallocated (most of the data inside was invalid but the size was different than 0). Other elements of `FVelocityAndPressureField` look correct, even `Element` which is also array view. Among places where the array could be reallocated/destroyed are `FTriangleMesh` move constructor and destructor, and a cleaning up it in `ResetAuxiliaryStructures`. Although the latter is called never for us.

Steps to Reproduce

Just noticed that a callstack was not attached by some reasons

TArray<Chaos::TVector<float,3>,TSizedDefaultAllocator<32>>::operator[](int) Line 829 C++ Chaos::Softs::FVelocityAndPressureField::Apply(Chaos::Softs::FSolverParticles & InParticles, const float Dt, const int Index) Line 66 C++ Chaos::Softs::FPBDEvolution::PreIterationUpdate::__l27::<lambda>(int i) Line 446 C++ Invoke(Chaos::Softs::FPBDEvolution::PreIterationUpdate::__l27::void <lambda>(IConsoleVariable *) &) Line 47 C++ UE::Core::Private::Function::TFunctionRefCaller<Chaos::Softs::FPBDEvolution::PreIterationUpdate<1,1,1>‘::27'::void <lambda>(IConsoleVariable *),void __cdecl(int)>::Call(void * Obj, int & <Params_0>) Line 475 C++ UE::Core::Private::Function::TFunctionRefBase<UE::Core::Private::Function::FFunctionRefStoragePolicy,void __cdecl(int)>::operator()(int <Params_0>) Line 600 C++ Chaos::PhysicsParallelFor::__l2::<lambda>(int Idx) Line 75 C++ Invoke(Chaos::PhysicsParallelFor::__l2::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &) &) Line 47 C++ UE::Core::Private::Function::TFunctionRefCaller<Chaos::PhysicsParallelFor’::2'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),void __cdecl(int)>::Call(void * Obj, int & <Params_0>) Line 475 C++ UE::Core::Private::Function::TFunctionRefBase<UE::Core::Private::Function::FFunctionRefStoragePolicy,void __cdecl(int)>::operator()(int <Params_0>) Line 600 C++ ParallelForImpl::CallBody(const TFunctionRef<void __cdecl(int)> &) Line 80 C++ ParallelForImpl::ParallelForInternal<TFunctionRef<void __cdecl(int)>,ParallelFor’::2'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),std::nullptr_t>(const wchar_t * DebugName, int Num, int MinBatchSize, TFunctionRef<void __cdecl(int)> Body, ParallelFor::__l2::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &) CurrentThreadWorkToDoBeforeHelping, EParallelForFlags Flags, const TArrayView<std::nullptr_t,int> & Contexts) Line 137 C++ ParallelFor(int Num, TFunctionRef<void __cdecl(int)> Body, bool bForceSingleThread, bool bPumpRenderingThread) Line 455 C++ Chaos::PhysicsParallelFor(int InNum, TFunctionRef<void __cdecl(int)> InCallable, bool bForceSingleThreaded) Line 76 C++ Chaos::Softs::FPBDEvolution::PreIterationUpdate<1,1,1>(const float Dt, const int Offset, const int Range, const int MinParallelBatchSize) Line 423 C++ Chaos::Softs::FPBDEvolution::AdvanceOneTimeStep::__l3::<lambda>(Chaos::Softs::FSolverParticles & Particles, int Offset, int Range) Line 499 C++ Invoke(Chaos::Softs::FPBDEvolution::AdvanceOneTimeStep::__l3::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &) &) Line 47 C++ UE::Core::Private::Function::TFunctionRefCaller<Chaos::Softs::FPBDEvolution::AdvanceOneTimeStep’::3'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),void __cdecl(Chaos::Softs::FSolverParticles &,int,int)>::Call(void * Obj, Chaos::Softs::FSolverParticles & <Params_0>, int & <Params_1>, int & <Params_2>) Line 475 C++ UE::Core::Private::Function::TFunctionRefBase<UE::Core::Private::Function::FFunctionRefStoragePolicy,void __cdecl(Chaos::Softs::FSolverParticles &,int,int)>::operator()(Chaos::Softs::FSolverParticles & <Params_0>, int <Params_1>, int <Params_2>) Line 601 C++ Chaos::TPBDActiveView<Chaos::Softs::FSolverParticles>::RangeFor::__l2::<lambda>(int RangeIndex) Line 198 C++ Invoke(Chaos::TPBDActiveView<Chaos::Softs::FSolverParticles>::RangeFor::__l2::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &) &) Line 47 C++ UE::Core::Private::Function::TFunctionRefCaller<Chaos::TPBDActiveViewChaos::Softs::FSolverParticles::RangeFor’::2'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),void __cdecl(int)>::Call(void * Obj, int & <Params_0>) Line 475 C++ UE::Core::Private::Function::TFunctionRefBase<UE::Core::Private::Function::FFunctionRefStoragePolicy,void __cdecl(int)>::operator()(int <Params_0>) Line 600 C++ Chaos::PhysicsParallelFor::__l2::<lambda>(int Idx) Line 75 C++ Invoke(Chaos::PhysicsParallelFor::__l2::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &) &) Line 47 C++ UE::Core::Private::Function::TFunctionRefCaller<Chaos::PhysicsParallelFor’::2'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),void __cdecl(int)>::Call(void * Obj, int & <Params_0>) Line 475 C++ UE::Core::Private::Function::TFunctionRefBase<UE::Core::Private::Function::FFunctionRefStoragePolicy,void __cdecl(int)>::operator()(int <Params_0>) Line 600 C++ ParallelForImpl::CallBody(const TFunctionRef<void __cdecl(int)> &) Line 80 C++ ParallelForImpl::ParallelForInternal<TFunctionRef<void __cdecl(int)>,ParallelFor'::2’::void (const TArray<FString,TSizedDefaultAllocator<32>> &),std::nullptr_t>‘::2'::FParallelExecutor::operator()(const bool bIsMaster, const wchar_t * DebugName) Line 336 C++ LowLevelTasks::FTask::Init::__l5::<lambda>(const bool) Line 499 C++ Invoke(LowLevelTasks::FTask::Init::__l5::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &) &) Line 47 C++ LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48>::TTaskDelegateImpl<LowLevelTasks::FTask::Init<ParallelForImpl::ParallelForInternal<TFunctionRef<void __cdecl(int)>,ParallelFor’::2'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),std::nullptr_t>'::2’::FParallelExecutor>‘::5'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),0>::Call(void *) Line 162 C++ LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48>::TTaskDelegateImpl<LowLevelTasks::FTask::Init<ParallelForImpl::ParallelForInternal<TFunctionRef<void __cdecl(int)>,ParallelFor’::2'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),std::nullptr_t>'::2’::FParallelExecutor>‘::5'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),0>::CallAndMove(LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48> & Destination, void * InlineData, unsigned int DestInlineSize, bool <Params_0>) Line 171 C++ LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48>::CallAndMove(LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48> &) Line 308 C++ LowLevelTasks::FTask::ExecuteTask() Line 627 C++ LowLevelTasks::FScheduler::ExecuteTask(LowLevelTasks::FTask * & InOutTask) Line 151 C++ LowLevelTasks::FScheduler::TryExecuteTaskFrom<LowLevelTasks::TLocalQueueRegistry<1024>::TLocalQueue,&LowLevelTasks::TLocalQueueRegistry<1024>::TLocalQueue::DequeueGlobal,0>(LowLevelTasks::TLocalQueueRegistry<1024>::TLocalQueue * Queue, LowLevelTasks::TLocalQueueRegistry<1024>::FOutOfWork & OutOfWork, bool bPermitBackgroundWork, bool bDisableThrottleStealing) Line 350 C++ LowLevelTasks::FScheduler::WorkerMain(LowLevelTasks::FSleepEvent * WorkerEvent, LowLevelTasks::TLocalQueueRegistry<1024>::TLocalQueue * WorkerLocalQueue, unsigned int WaitCycles, bool bPermitBackgroundWork) Line 378 C++ LowLevelTasks::FScheduler::CreateWorker::__l2::<lambda>() Line 71 C++ Invoke(LowLevelTasks::FScheduler::CreateWorker::__l2::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &) &) Line 47 C++ UE::Core::Private::Function::TFunctionRefCaller<LowLevelTasks::FScheduler::CreateWorker’::2'::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &),void __cdecl(void)>::Call(void * Obj) Line 475 C++ UE::Core::Private::Function::TFunctionRefBase<UE::Core::Private::Function::TFunctionStorage<1>,void __cdecl(void)>::operator()() Line 600 C++ FThreadImpl::Run() Line 77 C++ FRunnableThreadWin::Run() Line 150 C++ FRunnableThreadWin::GuardedRun() Line 73 C++ FRunnableThreadWin::_ThreadProc(void * pThis) Line 39 C++

A bit more details I found. I added debug FTriangleMesh pointer and some debug facilities to

FVelocityAndPressureField to make sure FTriangleMesh is still alive, because PointToTriangleMap is an array view to FTriangleMesh::MPointToTriangleMap. When a new crash happened, it was alive which is a good news.

The view contained 103 elements, and only [88-103) looked valid. At same time the mesh contained an array with 15 elements which are same as in view range [88-103). What actually wrong is either a view or the

Index. Because that index came from ParallelFor which parallelized valid ranges, first of which was [0-88). It came from TPBDActiveView<FSolverParticles>::RangeFor (variable MParticlesActiveView), and there are two valid ranges: [0-88) and [88-103).

Hi Anton,

I know there are likely to be quite a few fixes if you are on 5.2 at the moment. Could be worth taking that CL and see if that helps as a first step.

If not, how difficult would it be to create a repro project on 5.2 vanilla? If so I can bisect our codebase and find the CL which fixes the issue (which would be my assumption at this point).

Best

Geoff Stacey

Developer Relations

EPIC Games

Great to hear Anton,

I’ll close this off in that case, but if you need any further assistance please reach out !

Geoff

It seems that CL39158925 would be helpful:

Make TPBDActiveView threadsafe when activate/deactivating ranges in parallel (how they are used by Cloth).

Hi Geoff, it seems CL39158925 helped us