|
|
Есть такой жанр, сродни эпистолярному - называется "переписка
в средствах массовой информации". Особенно тяжелый случай - переписка
в разных изданиях :-)
На меня сослался Igor's digest,
на тему дырки в outlook express, ну, и поразоблачал заблуждения немного.
Не спорю - заблуждаюсь я часто и регулярно, но стараюсь
делать это не слишком глубоко. :-) А теперь - к заблуждениям :-)
"Заблуждения, если быть точным, на самом деле два. Первое - очевидное. Проблема из серии buffer overflow не имеет отношения ни к самим строкам, ни к их представлению. Появилась эта весьма дорогостоящая человечеству прореха исключительно из-за особенности реализации компиляторов. Язык Си в данном случае - не единственно "опасный". Вот решили, не подумавши о последствиях, размещать локальные переменные функций (говоря терминами Си - автоматические переменные) в стеке. Вариант быстрый, эффективный и... очень ненадежный"
Л-логично. Но. Я и не говорил, что buffer overflow однозначно связан с Сишными
строками - только лишь заявил, что большой процент случаев "перееха буфера"
связан именно со строками. Ну, исторически так сложилось что-ли,
что стандартная реализация сишных строк способствует их overrun'у и прочим
приятностям. Далее. Реализация - это хорошо, но. Попробуйте-ка
в, скажем, Паскале, с включенной проверкой выхода за границы массива,
сделать переполнение буфера, желательно без использования "сторонних" функций
(включая системнное API - ему-то пофиг куда писать, оно само на Сях писано).
Скорее всего это таки получится сделать,
но не без применения каких-либо ухищрений. А теперь покажите
мне реализацию Си, в которой я не смогу затереть "соседний" массив,
выйдя за пределы или поэкспериментировав с указателями. Впрочем, опять-таки,
возможно такие и есть, но можно ли их называть "языком Си", учитывая
что "грязные трюки" с указателями в Си есть штатная и разрешенная вещь.
Далее. Я в курсе про стек, и в курсе про адрес возврата. Даже соглашусь,
что возможность достучаться до адреса возврата через переполнение чего-либо
есть куда большая дыра в идеологии, чем какие-то сишные строки и
непроверка границ массива. Но - пока вокруг нас винды борются с юниксами,
адрес возврата в стеке - объективная реальность, которая, как мы помним, существует
независимо от нашего сознания. Вот будут распространены системы, где переполнение буфера не будет
приводить к "срыву стека" - тогда уж разберемся.
"Второе заблуждение связано с тем, что в языке Си строк нет вообще. Керниган и Ричи ничего не решали: базовый тип char* является обычным указателем на память с увеличением/уменьшением на указанное количество sizeof(char) (не единиц! :-) при арифметических операциях над ним. Вот и все. Ассоциировать этот тип со строками не имеет ни малейшего смысла, ибо язык Си есть язык очень низкого уровня. Представление строк, дат, реляционных кортежей, содержания web-страниц и т.п. находится в ведении только лишь разработчиков."
А тут фигня вот в чем. В том, что строки в языке есть.
Чтобы в этом убедиться, достаточно прочитать любую книжку по Си - там будет глава
"работа со строками", с обязательными strcat(), strcpy() и прочим. Вот
"реляционных кортежей" действительно нет, да и вообще, структур
сложнее числовых да адресных штатно не предусмотрено. А значит,
дыры в реализации "реляционных кортежей" или библиотек работы со сложнейшей структурой
complex (состоящей из {double re; double im;})
будут проблемой разработчиков, но не языка. А вот char*... Поскольку
str*() функции стандартно входят в clib - то и строки в Си есть.
Поскольку эти странные создания типа char* сплошь и рядом передаются
таким необходимым функциям, как fopen(), {f|s}printf() и что совсем плохо - {f|s}scanf()
- отказаться от них среднему программисту и написать что-то свое будет
морально тяжело. Ну, а что не только программисты готовы пользоваться
тем, что "пусть кривое, но уже есть и стандартно", чем искать прямое
но нестандартное или писать своё - известно.
Ну, а раз уж создатели языка изначально заложили потенциально опасную реализацию
"стандартных" строк, то результат более-менее очевиден. И пусть есть
strn*(), и пусть в том же *scanf можно явно ограничивать длину вводимой строки -
но это же нужно помнить, держать в голове, и не ошибаться при наборе цифири.
А точнее - обвешивать все вызовы кружевами из sizeof(s) и... даже не знаю
уж, как политкорректно вставить sizeof внутрь форматной строки sscanf.
А любое усложнение жизни либо отвергается (ну зачем тогда вообще
сделали gets(), когда fngets() заведомо безопаснее и столь же функционален),
либо приводит к новым ошибкам.
И даже если святые К и Р, придумывая str*() и gets() рассчитывали на то,
что программисты быстренько поймут их нежелательность и нарисуют свою,
безопасную, обвязку для всего этого, эти ожидания явно не сбылись.
Да и с чего бы им сбываться?
| |
| |