Spec-Zone.ru › Haskell 9

6.18. Безопасный Haskell

Безопасный Haskell — это расширение языка Haskell, реализованное в GHC начиная с версии 7.2. Оно позволяет безопасно включать небезопасный код в доверенную базу кода, ограничивая возможности GHC Haskell, используемые этим кодом. Проще говоря, он делает типы программ доверенными.

Хотя основное применение Безопасного Haskell — это выполнение недоверенного кода, Безопасный Haskell не предоставляет это напрямую. Вместо этого, Безопасный Haskell обеспечивает строгую типовую безопасность. Без Безопасного Haskell GHC допускает множество исключений из системы типов, которые могут нарушить любые абстракции. Обеспечивая строгую типовую безопасность, Безопасный Haskell позволяет разработчикам создавать собственные механизмы песочницы на уровне библиотек для выполнения недоверенного кода.

Хотя Безопасный Haskell является расширением, он фактически работает в фоновом режиме для каждой компиляции с GHC. Он делает это для отслеживания нарушений типов модулей, чтобы определить их безопасность, даже если они явно не используют Безопасный Haskell. Подробнее об этом см. в разделе Вывод Безопасного Haskell.

Конструкции Безопасного Haskell охватывают следующие аспекты:

  • Диалект языка Haskell безопасный язык, обеспечивающий более строгие гарантии относительно кода. Он позволяет доверять типам и границам модулей.
  • Расширение безопасного импорта, указывающее, что импортируемый модуль должен быть доверенным.
  • Определение доверия (или безопасности) и его работы, а также способы определения и изменения доверия к модулям и пакетам.

Однако Безопасный Haskell не предлагает компиляционной безопасности. В процессе компиляции возможно запускать произвольные процессы, например, используя флаг настраиваемого препроцессора. Это можно использовать для компрометации системы пользователя во время компиляции или для изменения исходного кода непосредственно перед компиляцией, чтобы попытаться изменить флаги Безопасного Haskell. Это обсуждается более подробно в разделе Безопасная компиляция.

6.18.1. Использование Безопасного Haskell

Безопасный Haskell был разработан с учетом двух вариантов использования:

  • Принуждение к строгой типовой безопасности во время компиляции
  • Компиляция и выполнение недоверенного кода

6.18.1.1. Строгая типовая безопасность (хороший стиль)

Haskell предлагает мощную систему типов и разделение чистых и эффектных функций через IO монаду. Однако в системе типов есть несколько лазеек, наиболее очевидной из которых является функция unsafePerformIO :: IO a -> a. Диалект безопасного языка Безопасного Haskell запрещает использование таких функций. Это может быть полезным ограничением, так как делает код Haskell проще для анализа и обоснования. Это также кодифицирует существующую культуру в сообществе Haskell по избеганию небезопасных функций, если это абсолютно необходимо. Таким образом, использование безопасного языка (через флаг -XSafe) можно рассматривать как способ обеспечения хорошего стиля, аналогично функции -Wall.

6.18.1.2. Создание безопасных систем (ограниченные монады ввода/вывода)

Системы, такие как безопасность контроля потока информации, системы безопасности на основе возможностей и DSL для работы с зашифрованными данными и т.д., могут быть созданы на языке Haskell в виде библиотеки. Однако они требуют гарантий о свойствах Haskell, которые неверны в общем случае из-за наличия функций, таких как unsafePerformIO. Безопасный Haskell предоставляет пользователям достаточные гарантии системы типов, чтобы позволить им создавать такие безопасные системы.

Например, давайте определим интерфейс системы плагинов, где авторы плагинов являются недоверенными, возможно, злонамеренными третьими лицами. Мы делаем это, ограничивая интерфейс плагина чистыми функциями или ограниченной IO монадой, которую мы определили. Ограниченная IO монада позволит выполнять только безопасный подмножество IO действий. Мы определяем интерфейс плагина таким образом, что он требует от модуля плагина Danger, экспортировать одно вычисление Danger.runMe, типа RIO (), где RIO — это монада, определённая следующим образом:

-- While we use `Safe', the `Trustworthy' pragma would also be
-- fine. We simply want to ensure that:
-- 1) The module exports an interface that untrusted code can't
--    abuse.
-- 2) Untrusted code can import this module.
--
{-# LANGUAGE Safe #-}

module RIO (RIO(), runRIO, rioReadFile, rioWriteFile) where

-- Notice that symbol UnsafeRIO is not exported from this module!
newtype RIO a = UnsafeRIO { runRIO :: IO a }

instance Functor RIO where
    fmap f (UnsafeRIO m) = UnsafeRIO (fmap f m)

instance Applicative RIO where
    pure = UnsafeRIO . pure
    (UnsafeRIO f) <*> (UnsafeRIO m) = UnsafeRIO (f <*> m)

instance Monad RIO where
    (UnsafeRIO m) >>= k = UnsafeRIO $ m >>= runRIO . k

-- Returns True iff access is allowed to file name
pathOK :: FilePath -> IO Bool
pathOK file = {- Implement some policy based on file name -}

rioReadFile :: FilePath -> RIO String
rioReadFile file = UnsafeRIO $ do
    ok <- pathOK file
    if ok then readFile file else return ""

rioWriteFile :: FilePath -> String -> RIO ()
rioWriteFile file contents = UnsafeRIO $ do
    ok <- pathOK file
    if ok then writeFile file contents else return ()

Затем мы компилируем плагин Danger с помощью нового флага Безопасного Haskell -XSafe:

{-# LANGUAGE Safe #-}
module Danger ( runMe ) where

runMe :: RIO ()
runMe = ...

Прежде чем перейти к деталям Безопасного Haskell, давайте отметим некоторые причины, по которым этот механизм безопасности потерпит неудачу без Безопасного Haskell:

  • Этот дизайн пытается ограничить операции, которые может выполнить Danger, используя типы, в частности обертку типа RIO над IO. Однако автор Danger может обойти это, просто написав произвольные IO действия и используя unsafePerformIO :: IO a -> a для их выполнения как чистых функций.
  • Этот дизайн также полагается на Danger не имея возможности получить доступ к конструктору UnsafeRIO. К сожалению, Template Haskell может использоваться для обхода границ модулей, а значит, и для получения доступа к этому конструктору.
  • Нет способа ограничить модули, которые может импортировать Danger. Это дает автору Danger очень большую поверхность атаки, по существу, любой пакет, установленный в системе. Если какой-либо из этих пакетов имеет уязвимость, то модуль Danger может использовать ее.

Безопасный Haskell предотвращает все эти атаки. Это делается путём компиляции модуля RIO с флагом Safe или Trustworthy и компиляции Danger с флагом Safe. Мы объясним каждый из них ниже.

Использование Safe для компиляции Danger ограничивает возможности Haskell, которые могут использоваться, до безопасного подмножества. Это включает запрет unsafePerformIO, Template Haskell, чистых функций FFI, правил и ограничение работы перекрывающихся экземпляров. Флаг Safe также ограничивает модули, которые может импортировать Danger, только теми, которые считаются доверенными. Доверенные модули — это модули, скомпилированные с флагом Safe, где GHC предоставляет механическую гарантию безопасности кода. Или модули, скомпилированные с флагом Trustworthy, где автор модуля утверждает, что модуль безопасен.

Вот почему модуль RIO компилируется с флагом Safe или Trustworthy, чтобы позволить модулю Danger импортировать его. Флаг Trustworthy не накладывает никаких ограничений на модуль, как Safe (кроме ограничения перекрывающихся экземпляров до безопасных перекрывающихся экземпляров). Вместо этого автор модуля заявляет, что, хотя код может использовать небезопасные функции внутри, он экспонирует только API, который может использоваться безопасным способом.

Однако неограниченное использование флага Trustworthy является проблемой, поскольку произвольный модуль может использовать его, чтобы пометить себя как доверенный, но Trustworthy не предоставляет никаких гарантий о модуле, в отличие от Safe. Для контроля использования доверенных модулей рекомендуется использовать флаг -fpackage-trust. Этот флаг добавляет дополнительное требование к проверке доверия для доверенных модулей. Он требует, чтобы для того, чтобы доверенный модуль считался доверенным и мог использоваться в коде, скомпилированном с флагом Safe, клиент C, компилирующий код, должен сообщить GHC, что он доверяет пакету, в котором находится доверенный модуль. Это по существу способ сказать C, что, хотя этот пакет содержит доверенные модули, которые могут быть использованы недоверенными модулями, скомпилированными с Safe, я доверяю автору(ам) этого пакета и доверяю, что модули экспонируют только безопасный API. Доверие к пакету может быть изменено в любое время, поэтому, если в пакете найдена уязвимость, C может объявить пакет недоверенным, чтобы любая последующая компиляция против этого пакета завершилась неудачей. Более подробный обзор этого механизма см. в разделе Доверие и режимы Безопасного Haskell.

В примере Danger может импортировать модуль RIO, потому что RIO скомпилирован с флагом Safe. Таким образом, Danger может использовать функции rioReadFile и rioWriteFile для доступа к разрешенным именам файлов. Основное приложение затем импортирует как RIO, так и Danger. Для запуска плагина оно вызывает RIO.runRIO Danger.runMe в монаде IO. Приложение находится в безопасности, зная, что единственные IO будут касаться файлов, пути к которым были одобрены тестом pathOK.

Проверки Безопасного Haskell можно отключить для модуля, передав флаг -fno-safe-haskell. Это особенно полезно при компиляции с плагинами исходного кода, так как запуск плагина отмечает модуль как небезопасный и может вызвать неудачу проверок безопасности в последующих модулях.

6.18.2. Безопасный язык

Безопасный язык Haskell (включён с помощью -XSafe) гарантирует следующие свойства:

  • Референциальная прозрачность — Типы можно доверять. Любая чистая функция гарантированно является чистой. Их вычисление детерминировано и не приведёт к побочным эффектам. Функции в IO монаде всё ещё разрешены и ведут себя как обычно. Таким образом, например, функция unsafePerformIO :: IO a -> a запрещена в безопасном языке для обеспечения этого свойства.
  • Контроль границ модулей — В безопасном языке можно получить доступ только к символам, которые публично доступны через списки экспорта других модулей. Значения, использующие конструкторы данных, не экспортированные определяющим модулем, не могут быть просмотрены или созданы. Таким образом, если модуль M устанавливает некоторые инварианты посредством тщательного использования своего списка экспорта, то код, написанный на безопасном языке и импортирующий M, гарантированно будет соблюдать эти инварианты.
  • Семантическая непротиворечивость — Для любого модуля, импортирующего модуль, написанный на безопасном языке, выражения, которые компилируются как с безопасным, так и без безопасного импорта, имеют одинаковое значение в обоих случаях. То есть, импорт модуля, написанного на безопасном языке, не может изменить смысл существующего кода, который не зависит от этого модуля. Например, существуют некоторые ограничения на использование Перекрывающихся экземпляров, так как они могут нарушить это свойство.
  • Строгое подмножество — Безопасный язык строго является подмножеством Haskell, реализованного GHC. Любое выражение, которое компилируется на безопасном языке, имеет тот же смысл, что и при компиляции в обычном Haskell.

Эти четыре свойства гарантируют, что в безопасном языке можно доверять типам, можно доверять, что списки экспорта модулей соблюдаются, и можно доверять, что код, успешно скомпилированный, имеет тот же смысл, что и обычно.

Для достижения этих свойств в диалекте безопасного языка мы полностью отключаем следующие функции:

  • TemplateHaskell — Может использоваться для получения доступа к конструкторам и абстрактным типам данных, которые не были экспортированы модулем, тем самым нарушая границы модулей.

Кроме того, мы ограничиваем следующие функции:

  • ForeignFunctionInterface — Объявления внешнего импорта, импортирующие функцию с типом, отличным от IO, запрещены.
  • RULES — Правила переписывания, определённые в модуле M, скомпилированном с помощью Safe, удаляются. Правила, определённые в надёжных модулях, которые M импортирует, всё ещё действительны и будут срабатывать как обычно.
  • OverlappingInstances — Нет ограничений на создание перекрывающихся экземпляров, но мы ограничиваем их использование в конкретном месте вызова. Это подробное ограничение, см. Безопасные перекрывающиеся экземпляры для получения подробной информации.
  • GeneralisedNewtypeDeriving и DerivingVia — GND и DerivingVia не разрешены в безопасном языке. Это связано со способностью нарушать границы модулей, когда авторы модулей забывают добавить анотации номинальных ролей к своим типам, как нужно. По этой причине модуль Data.Coerce также считается небезопасным. Мы надеемся найти здесь лучшее решение в будущем.
  • GHC.Generics — Рукописные экземпляры типа Generic не разрешены в безопасном Haskell. Такие экземпляры не строго небезопасны, но существует важный инвариант, что экземпляр Generic должен соответствовать структуре типа данных, для которого определён экземпляр, и разрешение вручную реализованных экземпляров Generic нарушит этот инвариант. Производные экземпляры (через расширение DeriveGeneric) всё ещё разрешены. Обратите внимание, что единственная разрешённая стратегия вывода для вывода Generic в безопасном Haskell — stock, так как другая стратегия (например, anyclass) создаст экземпляр, нарушающий инвариант.

    См. раздел генерическое программирование для получения более подробной информации.

6.18.2.1. Безопасные перекрывающиеся экземпляры

Из-за гарантии семантической непротиворечивости безопасного Haskell нам необходимо ограничить работу перекрывающихся экземпляров. Мы не ограничиваем их определение, так как это глобальное свойство, и мы не можем определить его, взглянув на один модуль. Вместо этого, когда модуль вызывает функцию, принадлежащую типу класса, мы проверяем, считается ли разрешение экземпляра «безопасным». Эта проверка выполняется для модулей, скомпилированных как с -XSafe, так и с -XTrustworthy.

Более конкретно, рассмотрим следующие модули:

{-# LANGUAGE Safe #-}
module Class (TC(..)) where
  class TC a where { op :: a -> String }

{-# LANGUAGE Safe #-}
module Dangerous (TC(..)) where
  import Class

  instance
    {-# OVERLAPS #-}
    TC [Int] where { op _ = "[Int]" }

{-# LANGUAGE Safe #-}
module TCB_Runner where
  import Class
  import Dangerous

  instance
    TC [a] where { op _ = "[a]" }

  f :: String
  f = op ([1,2,3,4] :: [Int])

Оба модуля Class и Dangerous будут компилироваться с Safe без проблем. Однако в модуле TCB_Runner, мы должны проверить, является ли вызов op в функции f безопасным.

Что означает «безопасный»? Это означает, что импорт модуля, скомпилированного с Safe, не должен изменять смысл кода, который компилируется без импорта модуля. Это свойство безопасного Haskell, известное как семантическая непротиворечивость.

В нашей ситуации модуль TCB_Runner компилируется без импорта модуля Dangerous. Поэтому, при выборе экземпляра для вызова op, если мы определим экземпляр TC [Int] из модуля Dangerous как наиболее специфичный, это небезопасно. Это предотвращает изменение поведения нашего существующего кода кодом, написанным сторонними разработчиками, которым мы не доверяем (который скомпилирован с помощью -XSafe в безопасном Haskell).

В частности, мы применяем следующее правило для определения, является ли вызов метода класса небезопасным при наличии перекрывающихся экземпляров:

  • Наиболее специфичный экземпляр, Ix, определён в -XSafe скомпилированном модуле.
  • Ix является экземпляром-сиротой или экземпляром многопараметрического типа класса.
  • По крайней мере, один из перекрывающихся экземпляров, Iy, является одновременно:

    • Из другого модуля, чем Ix
    • Iy не помечен как OVERLAPPABLE

Это немного сложная эвристика, но она отражает ситуацию, когда импортируемый модуль N изменяет поведение существующего кода. Например, если второе условие не нарушается, то автор модуля M должен зависеть либо от типа класса, либо от типа, определённого в N.

Когда конкретный вызов метода класса считается небезопасным из-за перекрывающихся экземпляров, и модуль, который компилируется, использует Safe или Trustworthy, компиляция завершится неудачно. Для Unsafe ограничений не применяется, а для модулей с безопасным выводом они будут выведены как небезопасные.

6.18.3. Безопасные импорты

Безопасный Haskell расширяет обычный синтаксис импорта Haskell, добавляя ключевое слово safe:

impdecl -> import [safe] [qualified] modid [as modid] [impspec]

При использовании модуль, импортируемый с помощью ключевого слова safe, должен быть надёжным модулем, в противном случае произойдёт ошибка компиляции. Расширение безопасного импорта включено либо флагом -XSafe, либо -XTrustworthy, либо -XUnsafe. Когда используется флаг -XSafe, ключевое слово safe разрешено, но бессмысленно, так как каждый импорт обрабатывается как безопасный импорт.

6.18.4. Режимы доверия и Safe Haskell

Safe Haskell вводит следующие три флага языка:

  • Safe — Включает безопасный диалект языка, запрашивая от GHC гарантию доверия. Безопасный диалект языка требует, чтобы все импорты были доверенными, иначе произойдет ошибка компиляции. Safe Haskell также будет автоматически выводить этот тип безопасности для модулей, когда это возможно. Для получения более подробной информации см. раздел Вывод Safe Haskell.
  • Trustworthy — Это означает, что, хотя этот модуль может вызывать небезопасные функции внутри, автор модуля утверждает, что он экспортирует API, который нельзя использовать небезопасным способом. Это не включает безопасный язык. Однако это ограничивает разрешение перекрывающихся экземпляров, чтобы разрешить только безопасные перекрывающиеся экземпляры. Гарантия доверия предоставляется автором модуля, а не GHC. Оператор импорта с ключевым словом safe приводит к ошибке компиляции, если импортируемый модуль не является доверенным. Оператор импорта без ключевого слова ведет себя как обычно и может импортировать любой модуль, будь то доверенный или нет.
  • Unsafe — Помечает компилируемый модуль как небезопасный, чтобы модули, скомпилированные с использованием Safe, не могли его импортировать. Вы можете явно пометить модуль как небезопасный, когда он экспортирует внутренние конструкторы, которые могут быть использованы для нарушения инвариантов.

Хотя это флаги, они также соответствуют типам модулей Safe Haskell, которые может иметь модуль. Можно представить использование этих флагов как объявление явного контракта (или типа), которым должен обладать модуль. Если он недействителен, компиляция завершится ошибкой. GHC также выведет правильный тип для Safe Haskell, см. раздел Вывод Safe Haskell для получения более подробной информации.

Процедура проверки того, является ли модуль доверенным или нет, зависит от наличия флага -fpackage-trust. Проверка аналогична в обоих случаях, при этом флаг -fpackage-trust добавляет дополнительное требование для доверенных модулей, чтобы их рассматривали как доверенные.

6.18.4.1. Проверка доверия (-fpackage-trust отключен)

Модуль M в пакете P доверяется клиенту C тогда и только тогда, когда:

  • Выполняются оба условия:

    • Модуль был скомпилирован с использованием Safe
    • Все прямые импорты M доверяются C
  • или выполняются все эти условия:

    • Модуль был скомпилирован с использованием Trustworthy
    • Все прямые безопасные импорты M доверяются C

В приведенном выше определении доверия есть проблема. Любой модуль может быть скомпилирован с Trustworthy, и он будет считаться доверенным. Для управления этим существует дополнительное определение доверия к пакету (включено флагом -fpackage-trust). Цель доверия к пакету состоит в том, чтобы потребовать, чтобы клиент C явно указал, какие пакеты разрешено содержать доверенные модули. Доверенные пакеты считаются доверенными только в том случае, если они находятся в доверенном пакете.

6.18.4.2. Проверка доверия (-fpackage-trust включен)

Когда флаг -fpackage-trust включен, доверие к модулю зависит от того, доверен ли определенный пакет. Доверие к пакету определяется клиентом C, вызывающим GHC (то есть вами).

В частности, пакет P доверен, когда выполняется одно из следующих условий:

  • База данных пакетов C регистрирует, что P доверен (и никакие параметры командной строки это не переопределяют)
  • Флаги командной строки C указывают на доверие P независимо от того, что записано в базе данных пакетов.

В любом случае C является единственным авторитетом в отношении доверия к пакету. Клиент сам решает, каким пакетам он доверяет.

Когда используется флаг -fpackage-trust, модуль M из пакета P доверяется клиенту C тогда и только тогда, когда:

  • Выполняются оба условия:

    • Модуль был скомпилирован с Safe
    • Все прямые импорты M доверяются C
  • или выполняются все эти условия:

    • Модуль был скомпилирован с Trustworthy
    • Все прямые безопасные импорты M доверяются C
    • Пакет P доверяется C

Для первого определения доверия гарантия доверия предоставляется GHC благодаря ограничениям, налагаемым безопасным языком. Для второго определения доверия гарантия предоставляется первоначально автором модуля. Затем клиент C подтверждает доверие автору модуля, указав, что он доверяет пакету, в котором находится модуль. Эта цепочка доверия необходима, так как GHC не предоставляет гарантий для скомпилированных модулей Trustworthy.

Причина наличия двух режимов проверки доверия заключается в том, что дополнительное требование, включенное флагом -fpackage-trust, делает дизайн Safe Haskell вторгающимся. Пакеты, использующие Safe Haskell при включенном флаге, могут или не могут быть скомпилированы в зависимости от состояния доверенных пакетов на машине пользователя. Это и хрупко, и приводит к ошибкам компиляции для всех, даже если они не пытаются использовать гарантии, предоставляемые Safe Haskell. Отключение -fpackage-trust по умолчанию и преобразование его в флаг делает Safe Haskell расширением по выбору, а не всегда включенной функцией.

6.18.4.3. Пример

Package Wuggle:
    {-# LANGUAGE Safe #-}
    module Buggle where
        import Prelude
        f x = ...blah...

Package P:
    {-# LANGUAGE Trustworthy #-}
    module M where
        import System.IO.Unsafe
        import safe Buggle

Предположим, что клиент C решил доверять пакету P и пакету base. Тогда C доверяет ли модулю M? Ну, M помечен как Trustworthy, поэтому мы не ограничиваем язык. Однако мы все равно должны проверить импорты M:

  • Во-первых, M импортирует System.IO.Unsafe. Это небезопасный модуль, но M был скомпилирован с Trustworthy, поэтому автор P несет ответственность за этот импорт. C доверяет автору P, поэтому этот импорт допустим.
  • Во-вторых, M импортирует Buggle безопасно. В данном импорте автор P не несет ответственности за безопасность, а вместо этого просит GHC проверить, доверяет ли C модулю Buggle.
  • Buggle, скомпилирован с -XSafe, поэтому код проверяется машинно, чтобы убедиться в его корректности, но опять же при условии, что все импорты Buggle доверяются C. Мы должны рекурсивно проверить все импорты!
  • Buggle импортирует только Prelude, который скомпилирован с Trustworthy. Prelude находится в пакете base, которому C доверяет, и (мы предположим), что все импорты Prelude доверяются. Поэтому C доверяет Prelude, и поэтому C также доверяет Buggle. (Хотя Prelude обычно импортируется неявно, он все равно подчиняется тем же правилам, описанным здесь).

Обратите внимание, что C не нужно было доверять пакету Wuggle; машинная проверка достаточно. C нужно доверять только пакетам, содержащим Trustworthy модули.

6.18.4.4. Требования к доверенности

Авторы модулей, использующие расширение языка Trustworthy для модуля M, должны убедиться, что общедоступный API M (символы, экспортированные его списком экспорта) нельзя использовать небезопасным образом. Это означает, что экспортированные символы должны соответствовать типам безопасности и референциальной прозрачности.

6.18.4.5. Доверие к пакету

Safe Haskell присваивает пакетам новое булево свойство — доверие. Для указания свойства доверия пакетов доступны несколько новых опций в командной строке GHC:

-trust ⟨pkg⟩

Раскрывает пакет ⟨pkg⟩, если он был скрыт, и рассматривает его как доверенный пакет независимо от базы данных пакетов.

-distrust ⟨pkg⟩

Раскрывает пакет ⟨pkg⟩, если он был скрыт, и рассматривает его как недоверенный пакет независимо от базы данных пакетов.

-distrust-all-packages

Рассматривает все пакеты как недоверенные, если они не будут явно указаны как доверенные последующими параметрами командной строки.

Для установки свойства доверия пакета в базе данных пакетов см. Пакеты.

6.18.5. Вывод безопасного Haskell

В случае, когда модуль компилируется без одного из Safe, Trustworthy или Unsafe, GHC попытается сам определить, можно ли считать модуль безопасным. Этот вывод безопасности никогда не отметит модуль как надёжный, только как небезопасный или безопасный. GHC использует простой метод для определения этого для модуля M: если M будет компилироваться без ошибок с флагом Safe, то M отмечается как безопасный. В противном случае, он отмечается как небезопасный.

Когда следует использовать вывод безопасного Haskell, а когда следует использовать явный флаг Safe? В последнем случае следует использовать, когда есть жёсткое требование, чтобы модуль был безопасным. Это наиболее полезно для Сценариев использования безопасного Haskell: выполнение недоверенного кода. Вывод безопасности предназначен для обычных программистов Haskell. Пользователи, которым, вероятно, неважно Safe Haskell.

Авторы библиотек Haskell имеют выбор. Большинство просто используют вывод безопасности. Предполагая, что вы избегаете любых небезопасных функций языка, ваши модули будут отмечены как безопасные. Явный вывод против явного имеют следующие компромиссы:

  • Вывод — Это работает хорошо и не добавляет зависимостей от типа Safe Haskell любых модулей в других пакетах. Это означает, что тип Safe Haskell ваших собственных модулей может измениться без предупреждения, если изменится зависимость. Один из способов справиться с этим — использовать Флаги предупреждений Safe Haskell, которые будут предупреждать, если GHC выведет тип Safe Haskell, отличающийся от ожидаемого.
  • Явный — Это даёт вашей библиотеке стабильный тип Safe Haskell, на котором могут полагаться другие. Однако это увеличит вероятность сбоя компиляции при изменении зависимостей вашего пакета.

6.18.6. Резюме флагов Safe Haskell

Вкратце, Safe Haskell состоит из следующих трёх флагов языка:

Safe
Since:

7.2.1

Ограничивает модуль безопасным языком. Все прямые импорты модуля должны быть надёжными, но сам модуль не обязательно должен находиться в надёжном пакете, поскольку компилятор гарантирует его надёжность. Ключевое слово «safe» разрешено, но бессмысленно в операторах импорта, так как независимо от этого каждый импорт должен быть безопасным.

  • Модуль надёжен — Да
  • Язык Haskell — Ограничен безопасным языком
  • Импортированные модули — Все должны быть безопасными импортами, все должны быть надёжными.
Trustworthy
Since:

7.2.1

Это устанавливает, что модуль надёжен, но гарантия предоставляется автором модуля. Клиент этого модуля затем указывает, что он доверяет автору модуля, указав, что он доверяет пакету, содержащему модуль. Trustworthy не ограничивает модуль безопасным языком. Однако он ограничивает разрешение перекрывающихся экземпляров, разрешая только безопасные перекрывающиеся экземпляры. Он также позволяет использовать ключевое слово безопасного импорта.

  • Модуль надёжен — Да.
  • Модуль надёжен (-fpackage-trust включен) — Да, но только если пакет, в котором находится модуль, также надёжен.
  • Язык Haskell — Без ограничений, кроме как разрешены только безопасные перекрывающиеся экземпляры.
  • Импортированные модули — Под контролем автора модуля, какие из них должны быть надёжными.
Unsafe
Since:

7.4.1

Отметьте модуль как небезопасный, чтобы его нельзя было импортировать кодом, скомпилированным с Safe. Также включите расширение Safe Import, чтобы модуль мог потребовать доверия к зависимости.

  • Модуль надёжен — Нет
  • Язык Haskell — Без ограничений
  • Импортированные модули — Под контролем автора модуля, какие из них должны быть надёжными.

Флаг для отключения проверок Safe Haskell:

-fno-safe-haskell

Этот флаг можно включить, чтобы переопределить любой объявленный свойство безопасности модуля (Safe, Unsafe, Trustworthy), поэтому компиляция продолжается так, как если бы ни один из этих флагов не был указан. Это особенно полезно при компиляции с помощью плагинов, которые обычно приводят к тому, что скомпилированные модули отмечаются как небезопасные.

И один общий флаг:

-fpackage-trust

При включении выполняется дополнительная проверка надёжного модуля M, требующая, чтобы пакет, в котором M находится, считался надёжным, чтобы M считался надёжным.

И пять флагов предупреждений:

-Wunsafe

Выдать предупреждение, если компилируемый модуль считается небезопасным. Следует использовать для проверки типа безопасности модулей при использовании вывода безопасности.

-Wsafe

Выдать предупреждение, если компилируемый модуль считается безопасным. Следует использовать для проверки типа безопасности модулей при использовании вывода безопасности. Если модуль явно помечен как безопасный, предупреждение не будет выведено.

-Wtrustworthy-safe

Выдать предупреждение, если компилируемый модуль помечен как -XTrustworthy, но вместо этого может быть помечен как -XSafe, более информативная граница. Можно использовать для обнаружения того, как можно улучшить границу Safe Haskell по мере обновления зависимостей.

-Winferred-safe-imports
Since:

8.10.1

Default:

выключено

Модуль A ниже аннотирован как явное Safe, но он импортирует Safe-Inferred модуль.

{-# LANGUAGE Safe #-}
module A where

import B (double)

quad :: Int -> Int
quad = double . double


module B where

double :: Int -> Int
double n = n + n

Статус вывода изменчив: если в модуль B будет добавлен небезопасный импорт, это приведёт к ошибке компиляции A. При включении -Winferred-safe-imports, компилятор выдаст предупреждение об этом.

-Wmissing-safe-haskell-mode
Since:

8.10.1

Default:

выключено

Компилятор выдаст предупреждение, если ни один из Safe, Trustworthy или Unsafe не указан.

6.18.7. Безопасная компиляция

GHC включает ряд флагов, которые позволяют выполнять произвольные процессы во время компиляции. Одним из таких примеров является флаг пользовательского препроцессора. Другой — возможность Template Haskell выполнять код Haskell во время компиляции, включая действия ввода-вывода. Safe Haskell не решает эту проблему (хотя Template Haskell — запрещённая функция).

По этой причине рекомендуется, что при компиляции недоверенного исходного кода, который не был проверен вручную, следует принять следующие меры предосторожности:

  • Компилировать в песочнице, такой как chroot или аналогичная контейнерная технология. Или просто как пользователь с очень ограниченным доступом к системе.
  • Компилировать недоверенный код с флагом -XSafe на командной строке. Это гарантирует, что изменения компилируемого исходного кода не смогут отключить использование безопасного языка, поскольку флаг командной строки имеет приоритет над прагмой на уровне исходного кода.
  • Убедиться, что весь недоверенный код импортируется как безопасный импорт и что флаг -fpackage-trust (см. флаг) используется с пакетами из недоверенных источников, которые помечены как недоверенные.

Более подробное обсуждение проблем с безопасностью компиляции и некоторые потенциальные решения см. на википедии GHC.

Кроме того, использование аннотаций запрещено, так как это позволит обойти ограничения Safe Haskell. См. #10826 для получения дополнительной информации.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.12.1/docs/users_guide/exts/safe_haskell.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API