forked from emulek/sed
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ch02s04.html
162 lines (159 loc) · 24.3 KB
/
ch02s04.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>BUGS (Ошибки в sed).</title><link rel="stylesheet" href="chs/default.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.1"><link rel="home" href="index.html" title="Краткий учебник по sed."><link rel="up" href="ch02.html" title="Глава 2."><link rel="prev" href="ch02s03.html" title="Производительность и быстродействие sed."><link rel="next" href="ch02s05.html" title="sed FAQ (ЧАВО)"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">BUGS (Ошибки в sed).</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s03.html">Пред.</a> </td><th width="60%" align="center">Глава 2. </th><td width="20%" align="right"> <a accesskey="n" href="ch02s05.html">След.</a></td></tr></table><hr></div><div class="section" title="BUGS (Ошибки в sed)."><div class="titlepage"><div><div><h3 class="title"><a name="id2515127"></a><a name="bugs"></a>BUGS (Ошибки в sed).</h3></div></div></div><div class="section" title="Вступление (Эта программа работает криво! Что делать?)"><div class="titlepage"><div><div><h4 class="title"><a name="id2515135"></a>Вступление (Эта программа работает криво! Что делать?)</h4></div></div></div><p>
Ну скорее всего криво работает не программа, а ваш sed-скрипт. "Криво" - значит не так, как вы того хотите. Это вполне нормальное явление, и например у меня так бывает постоянно. Мало того, значительно чаще, чем скажем если я использую например C++. И тем не менее намного быстрее написать быстренько sed(bash/perl/...) скрипт, который с третьего раза, но выполнит задачу, чем мучится с составлением всяких ООПшных конструкций.
</p><p>
Для начала, авторы рекомендуют сначала изучить список известных "ошибок", которые из разряда "это не баг, а фича". Ну а я попытаюсь пояснить, почему-же это так сделано. Список представляет собой перевод файла BUGS из исходных текстов Gnu-sed version 4.2.
</p><p>
Куда слать баг-репорты?
</p><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3><p>
Bugs and comments may be sent to bonzini@gnu.org; please include in the Subject: header the first line of the output of ``sed --version''.
</p><p>
Please do not send a bug report like this:
</p><p>
</p><div class="blockquote"><blockquote class="blockquote"><p>
[while building frobme-1.3.4]
</p><p>
$ configure
</p><p>
sed: file sedscr line 1: Unknown option to 's'
</p><p>
If sed doesn't configure your favorite package, take a few extra minutes to identify the specific problem and make a stand-alone test case.
</p><p>
A stand-alone test case includes all the data necessary to perform the test, and the specific invocation of sed that causes the problem. The smaller a stand-alone test case is, the better. A test case should not involve something as far removed from sed as ``try to configure frobme-1.3.4''. Yes, that is in principle enough information to look for the bug, but that is not a very practical prospect.
</p></blockquote></div><p>
</p></div><p>
</p></div><div class="section" title="Команда N и последняя строка."><div class="titlepage"><div><div><h4 class="title"><a name="id2515230"></a>Команда N и последняя строка.</h4></div></div></div><p>
Многие версии sed завершают работу скрипта, если выполняется команда <span class="command"><strong>N</strong></span> для последней строки. Однако, GNU sed в данном случае распечатает значение буфера, если это не запрещено (например опцией <code class="option">-n</code>). И так-же прервёт выполнение скрипта.
</p><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
Данное поведение не очень логично, но очень удобно. На самом деле это поведение при ошибке, ведь мы читаем строку, которой нет. Если нам это не нужно, мы можем проверить, является-ли данная строка последней, и если это не так, то выполнить <span class="command"><strong>N</strong></span>.
</div><p>
См. также <a class="link" href="ch06s03.html#info_nn">info sed</a>, и <a class="link" href="ch04s07.html#t08">Пример</a> типичного использования команд D и N.
</p></div><div class="section" title="Синтаксис регулярных выражений (проблемы с обратным слешем)."><div class="titlepage"><div><div><h4 class="title"><a name="id2515291"></a>Синтаксис регулярных выражений (проблемы с обратным слешем).</h4></div></div></div><p>
sed использует базовые POSIX регулярные выражения. Кроме стандартных, GNU sed использует несколько своих спец-символов:
</p><pre class="programlisting">`\|', `\+', `\?', `\`', `\'', `\<', `\>', `\b', `\B', `\w', `\W'</pre><p>
</p><p>
Таким образом, RE /x\+/ совпадает в GNU-sed с одним или боле символов <span class="quote">«<span class="quote">x</span>»</span>, а в некоторых других sed это RE совпадает только с <span class="quote">«<span class="quote">x+</span>»</span>.
</p><p>
</p><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
Лично я практически всегда использую ERE, потому у меня символ <span class="quote">«<span class="quote">+</span>»</span> как раз и надо экранировать слешем, если я хочу, что-бы он воспринимался как обычный символ. Конечно приходится помнить про метасимволы вроде <span class="quote">«<span class="quote">\`</span>»</span>, которые приходится экранировать даже в ERE, впрочем такое встречается редко.
</div><p>
</p><p>
<a class="link" href="ch06s02.html#info_meta">см. также info sed</a>
</p></div><div class="section" title='sed -i портит файлы "только для чтения"!'><div class="titlepage"><div><div><h4 class="title"><a name="id2515367"></a><a name="bugs_i"></a>sed -i портит файлы "только для чтения"!</h4></div></div></div><p>
Короче:
</p><pre class="programlisting">sed d -i</pre><p>
удаляет содержимое файла. Даже если этот файл защищён от модификации. Это не ошибка. Это непонимание, как работают UNIX ФС.
</p><p>
Права на файл говорят о содержимом файла, а права на директорию - о списке файлов в этой директории. <span class="command"><strong>sed -i</strong></span> никогда не открывает файл для записи/модификации. Вместо этого она создаёт временную копию изменённого файла, который после редактирования переименовывает как старый, потому <span class="emphasis"><em>кажется</em></span>, что этот файл был изменён. На самом деле, это новый файл, который был создан на основе старого.
</p><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
Кстати странно, а как ещё должен работать <span class="emphasis"><em>потоковый</em></span> редактор? Да, он просто фильтрует поток, а <code class="option">-i</code> просто задаёт особое поведение для исходящего потока (после его сохранения в файле, этот файл переименовывается как исходный). Кстати, можно и не удалять источник, есть ещё возможность переименования источника по шаблону. Подробнее см. <a class="link" href="ch06.html#info_oi">info sed</a>.
</div><p>
</p><p>
</p><div class="warning" title="Внимание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Внимание</h3>
Безусловно, данное поведение опасно - злоумышленник может испортить нам любой файл, по своему произволу! Достаточно права модификации директории. Однако проблема здесь вовсе не в sed - злоумышленнику ничего не стоит скопировать этот файл, а потом переименовать.
</div><p>
</p></div><div class="section" title="Выражение /[А-Я]/ не совпадает с буквами русского алфавита!, а команда s/.*// не стирает буфер!"><div class="titlepage"><div><div><h4 class="title"><a name="id2515471"></a>Выражение /[А-Я]/ не совпадает с буквами русского алфавита!, а команда s/.*// не стирает буфер!</h4></div></div></div><p>
Да. Я знаю... И разработчики sed тоже в курсе... Вот только проблема тут вовсе и не в sed, и даже не в GNU Linux... Данная проблема проявляется исключительно при использовании национальных локалей, которые не совпадают с POSIX, или что тоже самое с C. Или, говоря по иному, при использовании любых алфавитных символов не из диапазона /[A-Za-z]/. С первой проблемой справится довольно просто: можно записать не /А-Я/, а
</p><pre class="programlisting">/[АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯЁ]/</pre><p>
Это конечно довольно криво, но что делать? За то это выражение отлично работает, и всегда совпадает с русскими большими буквами. При этом конечно подразумевается, что sed-скрипт, и обрабатываемый текст в одной и той-же кодировке (при использовании UTF-8 текст и скрипт может быть на любом языке мира, причём одновременно на разных языках). Проблема тут в том, что русские буквы вовсе не обязательно идут по алфавиту, потому сравнивая их мы получаем неопределённый результат (на разных компьютерах результат может быть разным).
</p><p><a name="bugs_utf"></a>
Вторая проблема намного серьёзнее.
</p><div class="warning" title="Внимание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Внимание</h3>
Используя тот факт, что RE /.*/ не всегда совпадает с чем угодно, злоумышленник может протащить свой вредоносный код через любой фильтр основанный на регулярных выражениях! Т.е. практически куда угодно!
</div><p>
Проблема тут в том, что хотя метасимвол точка <span class="quote">«<span class="quote">.</span>»</span> безусловно совпадает с любым символом, он тем не менее вовсе не обязан совпадать с НЕСИМВОЛОМ! Алфавит UNICODE "дырявый", он содержит дыры, некоторые допустимые числа, которые не сопоставлены ни с каким символом. Поведение точки на таких несимволах не определено стандартом, и на практике точка НЕ совпадает с несимволом.
</p><p>
</p><div class="tip" title="Подсказка" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Подсказка</h3>
Отключить юникод можно <a class="link" href="ch02s02.html#utf8">так</a>.
</div><p>
</p><p>
Вот простой пример: нам надо отсечь всё, что лежит после <span class="quote">«<span class="quote">|</span>»</span>
</p><pre class="programlisting">$ echo -e "тест|вредоносный код" | sed 's/|.*//'
тест</pre><p>
Как видите, всё прекрасно работает... Однако:
</p><pre class="programlisting">$ echo -e "тест|\xD1вредоносный код" | sed 's/|.*//'
тест?вредоносный код</pre><p>
Ну вот... Вредоносный код успешно проник сквозь фильтр :-(
</p><div class="tip" title="Подсказка" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Подсказка</h3>
Кроме отключения юникода есть <a class="link" href="ch04s04.html#exa_crc">ещё одно решение</a>.
</div><p>
</p><p>
</p><div class="warning" title="Внимание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Внимание</h3>Это вовсе не проблема sed! Это проблема юникода. И единственный возможный метод избежать этой проблемы - отключить юникод.</div><p>
Проблема sed - в лёгкости, мощности, и чрезвычайной простоте: можно написать изящный, и простой пример уязвимости... Конечно код на языке perl, и тем более C будет намного более громоздким. Беда в том, что если вы пишете на C или PERL'е, то у вас уже включён юникод, и ваша программа так-же подвержена этой уязвимости. Попробуйте например вот эту программу на C:
</p><pre class="programlisting">
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <regex.h>
#define ERRBUF_SIZE 1024
#define NMATCH 10
void error(int err_code, regex_t *rs)<a class="co" name="c1" href="ch02s04.html#b1"><img src="images/callouts/1.png" alt="1" border="0"></a>
{
char errbuf[ERRBUF_SIZE];
if(!err_code) return;
regerror(err_code, rs, errbuf, ERRBUF_SIZE);
fprintf(stderr, "regex error: %s\n", errbuf);
regfree(rs);
_exit(err_code);
}
int main(void)
{
const char str[] = "тестов\xD1\xD1ая строчка. ABCDEFG";<a class="co" name="c2" href="ch02s04.html#b2"><img src="images/callouts/2.png" alt="2" border="0"></a>
const char regex[] = ".*";<a class="co" name="c3" href="ch02s04.html#b3"><img src="images/callouts/3.png" alt="3" border="0"></a>
regex_t rs;
regmatch_t pmatch[NMATCH];
char *lc = setlocale(LC_ALL, "ru_RU.UTF-8");<a class="co" name="c4" href="ch02s04.html#b4"><img src="images/callouts/4.png" alt="4" border="0"></a>
if(!lc) return 77;
printf("test string: `%s' (%s)\n", str, lc);
int err = regcomp(&rs, regex, REG_EXTENDED);
error(err, &rs);
const char *ps = str;
while(*ps)<a class="co" name="c5" href="ch02s04.html#b5"><img src="images/callouts/5.png" alt="5" border="0"></a>
{
err = regexec(&rs, ps, NMATCH, pmatch, 0);
error(err, &rs);
int j;
const char *ps2 = NULL;
for(j = 0; j < NMATCH; j++)
{
if(pmatch[j].rm_so < 0)
continue;
printf("Совпадение %d: %d...%d\t`", j, pmatch[j].rm_so, pmatch[j].rm_eo);
int i;
for(i = pmatch[j].rm_so; i < pmatch[j].rm_eo; i++)
printf("%c", ps[i]);
printf("'\n");
if(!ps2)
ps2 = ps + i;<a class="co" name="c6" href="ch02s04.html#b6"><img src="images/callouts/6.png" alt="6" border="0"></a>
}
ps = ps2;
if(!ps) break;<a class="co" name="c7" href="ch02s04.html#b7"><img src="images/callouts/7.png" alt="7" border="0"></a>
printf("остаток: `%s'\n", ps);
}
regfree(&rs);
return 0;
}</pre><p>
</p><div class="calloutlist"><table border="0" summary="Callout list"><tr><td width="5%" valign="top" align="left"><p><a name="b1"></a><a href="#c1"><img src="images/callouts/1.png" alt="1" border="0"></a> </p></td><td valign="top" align="left"><p>
Функция для проверки ошибок, не производит никаких действий если код ошибки равен нулю, иначе выдаёт сообщение об ошибке и прерывает программу.
</p></td></tr><tr><td width="5%" valign="top" align="left"><p><a name="b2"></a><a href="#c2"><img src="images/callouts/2.png" alt="2" border="0"></a> </p></td><td valign="top" align="left"><p>
Именно эта строчка предполагается введённой злоумышленником. В ней содержится НЕСИМВОЛЫ.
</p></td></tr><tr><td width="5%" valign="top" align="left"><p><a name="b3"></a><a href="#c3"><img src="images/callouts/3.png" alt="3" border="0"></a> </p></td><td valign="top" align="left"><p>
Это регулярное выражение, предполагается, что оно совпадает с любой строкой.
</p></td></tr><tr><td width="5%" valign="top" align="left"><p><a name="b4"></a><a href="#c4"><img src="images/callouts/4.png" alt="4" border="0"></a> </p></td><td valign="top" align="left"><p>
Тут мы устанавливаем локаль UTF-8.
</p></td></tr><tr><td width="5%" valign="top" align="left"><p><a name="b5"></a><a href="#c5"><img src="images/callouts/5.png" alt="5" border="0"></a> </p></td><td valign="top" align="left"><p>
Тут мы входим в цикл разбора строки, мы ищем совпадения, пока в строке есть хоть 1 символ.
</p></td></tr><tr><td width="5%" valign="top" align="left"><p><a name="b6"></a><a href="#c6"><img src="images/callouts/6.png" alt="6" border="0"></a> </p></td><td valign="top" align="left"><p>
Если это нулевое совпадение(в смысле, совпадение со всем шаблоном, а НЕ с под-шаблонами), то мы сохраняем позицию символа за концом совпадения. Если там больше ничего нет, то сохраняется позиция завершающего нулевого байта.
</p></td></tr><tr><td width="5%" valign="top" align="left"><p><a name="b7"></a><a href="#c7"><img src="images/callouts/7.png" alt="7" border="0"></a> </p></td><td valign="top" align="left"><p>
Здесь мы выходим из основного цикла, потому-что совпадений не было.
</p></td></tr></table></div><p>
Ага... Зависнет эта программа в бесконечном цикле... А всё потому, что выражение /.*/ не совпадает со всей строкой - строка некорректная. Ну и поведение программы тоже не предсказуемое. К сожалению, такое будет работать на <span class="emphasis"><em>любом</em></span> языке, лишь-бы он поддерживал юникод.
</p><p>
Программа зациклилась, потому-что она нашла совпадение, и это совпадение - пустая строка. Это произошло на второй итерации, на первой мы нашли всё что до не символа, а во второй - от несимвола и до несимвола. В итоге и в строке что-то есть, и что-то находится, но продвижения вперёд нет.
</p></div><div class="blockquote"><blockquote class="blockquote"><p>
Вы можете обсудить этот документ на <a class="ulink" href="http://emulek.tk/forum/viewtopic.php?f=19&t=5026" target="_top">форуме</a>. Текст предоставляется по лицензии <a class="ulink" href="http://www.gnu.org/licenses/fdl.html" target="_top">GNU Free Documentation License</a> (<a class="ulink" href="http://forum.lorcode.org/viewtopic.php?f=15&t=30" target="_top">Перевод лицензии GFDL</a>).
</p><p>
Вы можете пожертвовать небольшую сумму яндекс-денег на счёт <span class="command"><strong>41001666004238</strong></span> для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.
</p></blockquote></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch02s03.html">Пред.</a> </td><td width="20%" align="center"><a accesskey="u" href="ch02.html">Уровень выше</a></td><td width="40%" align="right"> <a accesskey="n" href="ch02s05.html">След.</a></td></tr><tr><td width="40%" align="left" valign="top">Производительность и быстродействие sed. </td><td width="20%" align="center"><a accesskey="h" href="index.html">Начало</a></td><td width="40%" align="right" valign="top"> sed FAQ (ЧАВО)</td></tr></table></div></body></html>