Почему правительство США запрещает использование динамических языков для защищенных проектов?


120

Я знаю некоторых людей, которые в настоящее время работают над проектом для вооруженных сил США (низкий уровень безопасности, данные типа не боевых человеческих ресурсов).

Начальное состояние кода проекта было передано военным для проверки, и они запустили программу через какой-то инструмент анализатора безопасности. Он вернул отчет об известных проблемах безопасности в коде и потребовал внесения изменений, которые необходимо было выполнить перед поставкой конечного продукта.

Одним из вопросов, которые необходимо было решить, было удаление части проекта, написанной на Ruby, поскольку это динамический язык.

Каковы причины / причины, по которым нельзя использовать динамический язык в безопасных условиях? Разве это правительство не спешит внедрять новые технологии? Или динамические языки представляют собой дополнительную угрозу безопасности по сравнению со статическими языками (например, C ++ или Java )?


56
Единственный способ узнать наверняка, если ваши знакомые спросят своих работодателей о причине. Но я могу рискнуть предположить: статическая проверка типов - это еще один уровень, который помогает корректности критически важного программного обеспечения. Конечно, это не избавит от ошибок, но это шаг в правильном направлении: компьютер делает часть работы за вас. (Да, я знаю, что это территория священных войн).
Андрес Ф.


75
Вам не нужно программное обеспечение для управления ракетами, написанное на PHP + JavaScript.
Тулаинс Кордова

16
Данные HR не "низкий уровень безопасности". Я ожидаю, что компания сохранит мою занятость и личные данные как можно безопаснее.
gbjbaanb

5
@gbjbaanb Я полагаю, что ОП означало, что гибель людей - это не худший вариант развития событий.
Андрес Ф.

Ответы:


126

Есть ряд «изящных» вещей, которые можно сделать в динамических языках, которые можно спрятать в частях кода, которые не сразу очевидны для другого программиста или аудитора в отношении функциональности данного фрагмента кода.

Рассмотрим эту последовательность в irb (интерактивная оболочка ruby):

irb(main):001:0> "bar".foo
NoMethodError: undefined method `foo' for "bar":String
        from (irb):1
        from /usr/bin/irb:12:in `<main>'
irb(main):002:0> class String
irb(main):003:1> def foo
irb(main):004:2> "foobar!"
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> "bar".foo
=> "foobar!"

То, что там произошло, я попытался вызвать метод fooв константе String. Это не удалось. Затем я открыл класс String, определил метод fooвозврата "foobar!"и затем вызвал его. Это сработало.

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

Многие другие динамические языки имеют схожие вещи, которые можно сделать. В Perl есть Tie :: Scalar, который может за кулисами изменить работу данного скаляра (это немного более очевидно и требует конкретной команды, которую вы можете видеть, но скаляр, передаваемый откуда-то еще, может быть проблемой). Если у вас есть доступ к поваренной книге Perl, посмотрите рецепт 13.15 - Создание магических переменных с помощью tie.

Из-за этих вещей (и других часто являющихся частью динамических языков) многие подходы к статическому анализу безопасности в коде не работают. Perl и Undecidability показывают, что это так, и указывают даже на такие тривиальные проблемы с подсветкой синтаксиса ( whatever / 25 ; # / ; die "this dies!";возникают проблемы, потому что whateverможно определить, принимать аргументы или нет во время выполнения, полностью нарушая подсветку синтаксиса или статический анализатор).


Это может стать еще более интересным в Ruby с возможностью доступа к среде, в которой было определено замыкание (см. YouTube: Сохранение разумности Ruby от RubyConf 2011 от Joshua Ballanco). Я узнал об этом видео из комментария Ars Technica от MouseTheLuckyDog .

Рассмотрим следующий код:

def mal(&block)
    puts ">:)"
    block.call
    t = block.binding.eval('(self.methods - Object.methods).sample')
    block.binding.eval <<-END
        def #{t.to_s}
          raise 'MWHWAHAW!'
        end
    END
end

class Foo
    def bar
        puts "bar"
    end

    def qux
        mal do
            puts "qux"
        end
    end
end

f = Foo.new
f.bar
f.qux

f.bar
f.qux

Этот код полностью виден, но malметод может быть где-то еще ... и с открытыми классами, конечно, он может быть переопределен где-то еще.

Запуск этого кода:

~ / $ ruby ​​foo.rb 
бар
> :)
QUX
бар
b.rb: 20: в "qux": MWHWAHAW! (Ошибка выполнения)
    из б.р .: 30: в `'
~ / $ ruby ​​foo.rb 
бар
> :)
QUX
бр .: 20: в "баре": MWHWAHAW! (Ошибка выполнения)
    из б.р .: 29: в `'

В этом коде замыкание получило доступ ко всем методам и другим привязкам, определенным в классе в этой области. Он выбрал случайный метод и переопределил его, чтобы вызвать исключение. (см. класс Binding в Ruby, чтобы понять, к чему этот объект имеет доступ)

Переменные, методы, значение self и, возможно, блок итератора, к которому можно получить доступ в этом контексте, сохраняются.

Более короткая версия, которая показывает переопределение переменной:

def mal(&block)
    block.call
    block.binding.eval('a = 43')
end

a = 42
puts a
mal do 
  puts 1
end
puts a

Который при запуске выдает:

42
1
43

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

Хорошо или плохо, жалуется на рубине или нет (есть использование , где можно было бы хотел , чтобы иметь возможность получить в среде методы (см Safe в Perl)), вопрос о том, «почему бы рубине быть ограничен в течение правительственного проекта "действительно получен ответ в этом видео, связанном выше.

Учитывая это:

  1. Ruby позволяет извлекать среду из любого замыкания
  2. Ruby фиксирует все привязки в области закрытия
  3. Ruby поддерживает все привязки как живые и изменяемые
  4. В Ruby новые привязки скрывают старые привязки (а не клонируют среду или запрещают повторную привязку)

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

Подробнее об этом можно прочитать в блоге Abstract Heresies . Особый пост о Схеме, где проходили такие дебаты. (относится к SO: Почему Scheme не поддерживает среды первого класса? )

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

Я надеюсь, что в этом разделе показан опасный аспект среды первого класса и почему будет предложено удалить Ruby из предоставленного решения. Дело не только в том, что Ruby является динамическим языком (как уже упоминалось: в других проектах разрешены другие динамические языки), а в том, что есть определенные проблемы, которые делают некоторые динамические языки еще более трудными для рассуждения.


3
Я не понимаю суть этого. Вы упоминаете класс Perl, который позволяет изменять поведение скаляров. Тем не менее, Perl широко используется, в том числе в безопасных средах. Просто наличие этих возможностей в языке не означает, что язык не может быть использован. В конкретном случае Ruby, скорее всего, тот факт, что целевая среда не поддерживает Ruby. Лично я никогда не видел, чтобы Ruby был доступен для использования в каких-либо системах, и я даже не уверен, есть ли он в списках утвержденного программного обеспечения.
Томас Оуэнс

17
@ThomasOwens - Мое понимание этого ответа заключается в том , что ключ "many approaches to static analysis of security in code doesn't work", поэтому он отклонил , потому что не может быть проанализирован (по этой группе, по крайней мере). Правильно ли я интерпретирую это, или это даже веская причина, чтобы отвергнуть это, я не знаю.
Бобсон

21
Не имея информации в списках утвержденных программ, я могу только догадываться о трудностях с динамическими языками. Однако я видел похожие проблемы с финансовым программным обеспечением и провалами проверок индустрии платежных карт, потому что в языке не было возможности предварительно провести статический анализ по вопросам безопасности. Я продемонстрировал два примера на динамических языках, где природа языка позволяет ему подорвать статический анализ. Я также указал, почему это, даже теоретически, никогда не может быть точным. Может быть, Perl разрешен в некоторых местах, а не в других, я могу только догадываться о причинах.

2
Вы также можете переопределить стандартные библиотечные функции на многих других языках (например, Obj-C, C, C ++).
Мартин Уикман,

12
Ну, методы расширения .NET НЕ совпадают с приведенными выше Ruby. Они просто создают более простой способ ввода статического класса. На самом деле они не добавляют метод в класс.
Грэм

50

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

Инструменты анализа безопасности обычно плохо справляются с динамическим поведением.

Например:

Запустите любой проект .NET, написанный с использованием современных функций, таких как ASP.NET MVC и Entity Framework, с помощью чего-то вроде Veracode и посмотрите, какой список ложных срабатываний вы получаете в своем отчете.

Veracode даже перечисляет многие базовые методы в базовых библиотеках .NET 4 как «неподдерживаемые фреймворки» как неподдерживаемые или только бета-версии, хотя большинству из них уже несколько лет.

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

В гражданских операциях, где компьютерные системы обычно не контролируют что-либо опасное или ужасно дорогое, вы должны обсудить ложные срабатывания, и они, как правило, принимаются как таковые.

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

В военной, авиационной, тяжелой промышленности и т. П. Системы могут управлять вещами, которые имеют ужасные режимы отказа этих систем, поэтому у них могут быть очень строгие правила относительно языков, компиляторов и т. Д.

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


4
И это только ложные срабатывания. Действительно тревожная вещь - это вероятность ложных негативов.
Стивен С

3
Честно говоря, мой опыт работы с этими инструментами в целом был ужасным. Вероятно, что-то вроде скорости от 1/200 до 1/1000, что стоит обсудить. Кроме того, когда я получаю ложный положительный ответ, я знаю, что он используется в тысячах мест в кодовой базе или в фреймворке, и он обнаружил это лишь несколько раз, но я не совсем уверен в себе. Проблема в том, что вы эффективно реализуете отрицательное доказательство при создании одного из этих инструментов, если только вы не начинаете с формального языка, такого как spec #.
Билл

33

Динамические языки могут использоваться в оборонных и военных приложениях. Я лично использовал и поставлял Perl и Python в приложениях DoD. Я также видел использование и развертывание PHP и JavaScript. По моему опыту, большая часть некомпилированного кода, который я видел, была сценариями оболочки и Perl, потому что требуемые среды одобрены и установлены на различных возможных целевых системах.

Тот факт, что эти языки являются динамическими, скорее всего, не проблема. Переводчики этих языков должны быть одобрены для использования в целевых системах. Если интерпретатор не одобрен для использования (или, возможно, так и есть, но он не развернут в целевых системах), язык не может быть использован. Использование данного интерпретатора (или любого приложения) в защищенной системе требует любого количества барьеров безопасности: анализ источника, возможность компиляции из источника для целевых сред, дополнительный анализ двоичных файлов, предотвращение конфликтов с существующей инфраструктурой и т. Д.


32

Я потратил некоторое время на собеседование с министерством обороны (DOD), чтобы написать код позиции для MMU F-16 . Не нарушая неразглашения: MMU - это компьютерный блок, который контролирует почти все функции F-16. (Очевидно) крайне важно, чтобы во время полета не возникало ошибок, таких как ошибки во время выполнения. Не менее важно, чтобы система выполняла вычислительные операции в реальном времени.

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

Благодаря функциям поддержки Ada, критически важным для безопасности, теперь он используется не только для военных приложений, но и в коммерческих проектах, где программная ошибка может иметь серьезные последствия, например, авионика и управление воздушным движением, коммерческие ракеты (например, Ariane 4 и 5), спутники и другие космические системы, железнодорожный транспорт и банковское дело. Например, системное программное обеспечение для управления самолетом в Боинге 777 было написано на языке Ада.

Я ненавижу цитировать слишком много, но это очень хорошо объясняет, почему именно статические языки (например, ADA) используются для таких проектов:

Поддерживается большое количество проверок во время компиляции, чтобы помочь избежать ошибок, которые не могли бы быть обнаружены до времени выполнения на некоторых других языках или потребовали бы явных проверок, которые будут добавлены в исходный код. Например, синтаксис требует явно названного закрытия блоков для предотвращения ошибок из-за несовпадения конечных токенов. Приверженность строгой типизации позволяет обнаруживать многие распространенные программные ошибки (неправильные параметры, нарушения диапазона, недопустимые ссылки, несовпадающие типы и т. Д.) Как во время компиляции, так и во время выполнения. Поскольку параллелизм является частью спецификации языка, компилятор может в некоторых случаях обнаруживать потенциальные взаимоблокировки. Компиляторы также обычно проверяют идентификаторы с ошибками, видимость пакетов, избыточные объявления и т. Д. И могут предоставлять предупреждения и полезные советы о том, как исправить ошибку.

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

Динамическое управление памятью Ada является высокоуровневым и безопасным для типов. Ада не имеет общих (и расплывчатых) «указателей»; и при этом неявно объявляет любой тип указателя. Вместо этого все динамическое выделение и освобождение памяти должно осуществляться через явно объявленные типы доступа. Каждый тип доступа имеет связанный пул хранения, который обрабатывает низкоуровневые детали управления памятью; программист может использовать пул хранения по умолчанию или определить новые (это особенно актуально для неоднородного доступа к памяти). Можно даже объявить несколько разных типов доступа, которые обозначают один и тот же тип, но используют разные пулы хранения. Кроме того, язык обеспечивает проверки доступности как во время компиляции, так и во время выполнения, что гарантирует, что значение доступа не может пережить тип объекта, на который он указывает.


3
«Из-за критически важных для безопасности функций поддержки Ada теперь [используется] в коммерческих ракетах (например, Ariane 4 и 5)» , конечно, первая Ariane 5 взорвалась из-за ошибки в программном обеспечении , поэтому серебряной пули нет.
Эндрю Маршалл

5
@AndrewMarshall: «Хотя в отчете указана программная ошибка как прямая причина, другие исследователи рассматривают причины как сбои проектирования системы и проблемы управления» - я серьезно сомневаюсь, что код, написанный на другом языке (например, Java или C ++), получил бы ракета на орбиту.
Мартин Шредер

@ MartinSchröder О, я не сомневаюсь, что Ада все еще превосходит других в этом приложении, просто отмечая, что это не защищает от дурака. Другой язык мог пропустить бесчисленные ошибки, которые просто не были бы возможны в Аде.
Эндрю Маршалл

13

И DoD, и NASA имеют долгую историю с ошибками программирования, которые обходятся им в миллиарды долларов. Оба учреждения приняли процессы, которые должны защитить их от повторения одних и тех же ошибок.

Is this the government being slow to adopting new technologies?

Это заблуждение - динамические языки - это не новая технология, они довольно старые. Проблема в том, что если у вас когда-либо возникла проблема, вызванная динамическим языком (например, слабой / динамической типизацией), и эта проблема стоила вам больших денег, вы могли бы принять политику, которая помешает вам повторить ту же ошибку снова - например, запрет на использование динамических языков в чувствительных системах.

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

Если безопасность касается, было бы необходимо увидеть фактический вариант использования. Например, я не думаю, что веб-страница Ruby on Rails будет автоматически менее безопасной, чем веб-страница Java.


2
ИМХО больше ошибок было «проглочено» из-за необнаруженных переполнений буфера, чем что-либо еще, что именно то, чего большинство динамических языков не допустят в первую очередь ... просто скажу
miraculixx

@miraculixx Да, есть причина, по которой Java / C # и подобные языки используются гораздо чаще, чем Ruby. Они обороняются - они все проверяют. В C / C ++ защита может быть усилена с помощью хороших стандартов кодирования. Вы также можете применить проверки для всего. Но можете ли вы представить написание конфиденциального приложения в ruby ​​или javascript? Возможность скрытых ошибок велика.
Sulthan

Действительно, я могу. Мы, вероятно, согласны с тем, что программное обеспечение должно быть тщательно протестировано, независимо от языка программирования. Чтобы избежать регрессий, тестирование лучше всего автоматизировать с помощью, например, модульного тестирования, BDD et al. Предполагая профессиональный подход (деликатное приложение, верно?), Достижение достаточного охвата тестированием является управляемым процессом, не оставленным на волю случая. При этом я сомневаюсь, что C / C ++ и Java имеют преимущество перед Ruby или Javascript с точки зрения скрытых ошибок. Навыки программиста? Вероятно, более технический с C ++, сомнительный с Java, но вряд ли проблема с языком. Более технический! = Качество продукта.
miraculixx

6

Я хотел бы добавить к существующим ответам, описав SA-CORE-2014-005 Drupal , который является критически важной уязвимостью, которая делает возможным внедрение SQL и, в конечном счете, выполнение произвольного кода. Это вызвано динамической типизацией PHP и слабыми правилами типизации во время выполнения.

Весь патч для этой проблемы:

-      foreach ($data as $i => $value) {
+      foreach (array_values($data) as $i => $value) {

Этот код является частью уровня абстракции SQL, предназначенного для предотвращения внедрения SQL. Он принимает запрос SQL с именованными параметрами и ассоциативный массив, который предоставляет значение для каждого именованного параметра. Значение может быть массивом, например WHERE x IN (val1, val2, val3), когда все три значения могут быть переданы как одно значение массива для одного именованного параметра.

Уязвимость возникает из-за того, что в коде предполагается, что $iв $i => $valueдолжно быть целочисленный индекс значения. Он продолжается и объединяет этот «индекс» непосредственно в SQL-запрос как часть имени параметра, потому что целые числа не нуждаются в экранировании, верно?

К сожалению для Drupal, PHP не предоставляет такой гарантии. Можно передать другой ассоциативный массив, ключи которого являются строками, и этот цикл успешно объединит строковый ключ в запрос, как есть (помните, что код думает, что он может быть целым числом).

Несмотря на то, что в статически типизированном языке существуют подобные ошибки, они маловероятны. Хороший разработчик может рассмотреть возможные варианты, $iпрежде чем объединять их в запрос. С языком со статической типизацией очень легко обеспечить, $iчтобы это было целое число, и в коде, чувствительном к безопасности, как это, это наверняка было бы сделано.

Кроме того, код фактически проверяет, является ли значение массивом, прежде чем выполнять итерации по элементам. И в этом заключается вторая часть сбоя, которая допускает эту уязвимость: и ассоциативный массив, и «нормальный» массив возвращают true для is_array. Хотя также верно, что в C # есть словари и массивы IEnumerable, сложно создать код, который бы связывал ключи словаря с индексами массивов, как это даже намеренно, не говоря уже о случайности.


2

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

Большинство инцидентов безопасности из-за вредоносного ввода (инъекции sql, переполнения буфера), вирусов, руткитов и троянов. Ни один язык не может защитить вас от этого.

Так что запрещение классов языков за «небезопасность» не является уважительной причиной.

Я подозреваю, что кто-то по какой-либо причине - информированный или нет - решил запретить эти языки. Через некоторое время это стало организационной правдой . Возможно, это было правдой в то время для некоторых проектов, но контрольные культуры не заинтересованы в изменении решений (признают, что они ошибались) и предпочитают простые правила. Они процветают на правилах и положениях, и не имеет значения, имеют ли они смысл или нет, важна воспринимаемая безопасность .

Это происходит все время в культурах контроля. Я вижу это более или менее ежедневно. Это не имеет смысла, но это так. Если вы хотите узнать больше об этой весьма актуальной теме, я рекомендую книгу Шнайдера « Альтернатива реинжиниринга ». Вот диаграмма культуры Майкла Сахото / Agilitrix , основанная на книге Шнайдера: введите описание изображения здесь


18
-1 Есть много веских технических причин, по которым язык будет выбран по сравнению с другим (в режиме реального времени, статическая типизация, проверки во время выполнения) для систем, важных для безопасности. Вы подразумеваете, что причины - 100% культура, мы против них, и произвольные, что IMO совершенно неверно в этом случае.
Майкл Джаспер

8
«Языки не являются ни безопасными, ни небезопасными» - см. Stackoverflow.com/a/14313277/602554 . Некоторые языки определенно «более безопасны», чем другие.
Майкл Джаспер

2
Обновил мой ответ, может быть, вам по вкусу. Я все еще верю, что насколько безопасна система, она зависит больше от кода, который вы пишете, чем от того, какой язык вы используете, хотя некоторые языки помогают избежать определенных проблем (хотя, возможно, и вводят другие).
Мартин Уикман

2
@MartinWickman: a) Инъекции SQL / HTML и переполнения буфера решаются некоторыми системами типов - у вас есть разные типы для экранированного и неэкранированного ввода, так что вы знаете, как и каким образом следует обращаться. б) не все вопросы безопасности в «защищенных проектах» обязательно означают компромисс. Я не хочу, чтобы в программном обеспечении самолета был какой-либо баг, даже если это не проблема «безопасности» (т. Е. Не может использоваться для управления системой).
Мацей Пехотка

5
-1 для фактических проблем точности. Эксплойты переполнения буфера - это проблема, специфичная для языка Си; вы никогда не слышите о них на языках, которые не позволяют вам размещать строковый буфер в стеке. И совсем нетрудно представить гипотетический диалект SQL, в котором использование параметров было не просто разрешено, а требовалось . SQL-инъекция была бы невозможна на этом языке. Так что да, правильно разработанный язык может защитить вас от нескольких распространенных типов атак.
Мейсон Уилер

2

Насколько я могу судить, официальная политика Министерства обороны вообще не запрещает динамические языки.

Стандарты для программного обеспечения, разработанного или закупленного Министерством обороны, обнародованы Агентством оборонных информационных систем (DISA). Их Безопасность приложений - Техническое руководство по внедрению безопасности приложений и безопасности разработки (STIG) не запрещает какой-либо конкретный язык. Он не упоминает Ruby, но упоминает Perl и Python, которые одинаково динамичны. Он упоминает их в контексте различных тем (следуя установленным стандартам кодирования, избегая уязвимостей при инъекции команд и т. Д.).

Вероятно, вы видите чрезмерно строгий инструмент сканирования (в STIG упоминается несколько разных, у каждого может быть своя интерпретация правил) и / или чрезмерно строгая интерпретация его выходных данных.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.