UB из-за автоматической очистки allocaArray c или нет? - PullRequest
1 голос
/ 09 мая 2020

у меня есть эта функция в моем коде, которая, кажется, работает нормально:

-- type declaration just for reference, i don't have it in my actual code
retrieveVulkanArray :: Storable a => (Ptr Word32 -> Ptr a -> IO b) -> IO (Ptr a, Int)
retrieveVulkanArray' f =
    alloca $ \arrCount -> do
        f arrCount vkNullPtr
        arrCount' <- fromIntegral <$> peek arrCount
        allocaArray arrCount' $ \resArray -> do
            f arrCount resArray
            pure (resArray, arrCount')

(для контекста это вспомогательная функция для получения массивов FFI из Vulkan API, например, f может быть vkEnumeratePhysicalDevices )

Когда я просматривал свой код, я заметил, что он возвращает resArray (который из описания allocaArray, кажется, действителен только внутри внутренней лямбды) вызывающей стороне. В C такой код будет неопределенным. Моя интуиция верна здесь или происходит что-то еще? В конце концов, вылетов еще не заметил :)

1 Ответ

2 голосов
/ 09 мая 2020

Тот факт, что она работает, определенно не доказывает ее правильность, на самом деле эта функция действительно очень неправильная.

alloca, а также allocaArray, выделит Haskell MutableByteArray# преобразовать его в указатель. Работайте с этим указателем, а затем убедитесь, что массив все еще жив, с помощью специальной функции touch#. Проблема в том, что как только вы потеряете ссылку на фактический MutableByteArray#, что происходит при выходе из alloca, G C очистит его, и Ptr a, который указывал на этот массив, больше не будет действительным. Итак, если вы продолжите чтение или запись в этот указатель Ptr a после того, как вы вернете его из retrieveVulkanArray, вы читаете / записываете в память, которая может использоваться чем-то еще, и теперь вы находитесь в реальной опасности segfault и всевозможных других мер безопасности. уязвимости, которые связаны с ним.

Правильный способ:

retrieveVulkanArray
  :: Storable a => (Ptr Word32 -> Ptr a -> IO b) -> IO (ForeignPtr a, Int)
retrieveVulkanArray' f =
    alloca $ \arrCount -> do
        f arrCount vkNullPtr
        arrCount' <- fromIntegral <$> peek arrCount
        resArray <- mallocForeignPtrArray arrCount'
        _ <- withForeignPtr resArray (f arrCount)
        pure (resArray, arrCount')

ForeignPtr a гарантирует, что вы можете работать с необработанными Ptr a при необходимости, не беспокоясь о памяти, которую он указывает to освобождается до тех пор, пока ForeignPtr не перестанет использоваться.

...