В этом сообщении буду собирать ссылки на ресурсы по Linux Crypto API и XFRM. Информации очень мало, а исходный код довольно многоуровневый.
The Linux Kernel Cryptographic API
Технология IpSec
Crypto API in kernel
Linux Crypto
[Дальше]
Некоторые проекты
Показаны сообщения с ярлыком kernel. Показать все сообщения
Показаны сообщения с ярлыком kernel. Показать все сообщения
пятница, 26 февраля 2010 г.
пятница, 14 августа 2009 г.
mmap для нулевого адреса
Прочитал на lor статью про новую уязвимость. Прочитал, что в качестве идеи эксплоита лежит возможность выполнить код по нулевому виртуальному адресу. Тут-то я и задумался... Каким образом пользовательское приложение может это (сформировать код по 0 VA) сделать? В эксплоите был код:
Этот код у меня в системе не работает... Начал смотреть дальше, и вот что выяснилось.
Здесь описывается тот факт, что для нужд wine (выполнения 16 битного кода) в ядре разрешается делать mmap на 0 адрес (/proc/sys/vm/mmap_min_addr). Самое интересное, что в статье утверждается, что в Ubuntu при установке wine эта возможность включается для системы в целом. Кроме того, в следствии особенностей архитектуры SELinux, по умолчанию, системы с включенным SELinux (как я понял) также разрешают пользователям делать mmap 0 адреса.
Интересно. :)
UPD: Кроме того, если в системе есть pulseaudio с установленным suid root, то эксплоит будет загружен через pulseaudio -L и mmap будет уже делаться от пользователя с euid == 0 (и выполнится успешно). Правда, в таком случае можно получить рута более простым способом -- так как мы и так уже получили привилегии администратора. Довольно нечестный прием. :) [Дальше]
mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0)
Этот код у меня в системе не работает... Начал смотреть дальше, и вот что выяснилось.
Здесь описывается тот факт, что для нужд wine (выполнения 16 битного кода) в ядре разрешается делать mmap на 0 адрес (/proc/sys/vm/mmap_min_addr). Самое интересное, что в статье утверждается, что в Ubuntu при установке wine эта возможность включается для системы в целом. Кроме того, в следствии особенностей архитектуры SELinux, по умолчанию, системы с включенным SELinux (как я понял) также разрешают пользователям делать mmap 0 адреса.
Интересно. :)
UPD: Кроме того, если в системе есть pulseaudio с установленным suid root, то эксплоит будет загружен через pulseaudio -L и mmap будет уже делаться от пользователя с euid == 0 (и выполнится успешно). Правда, в таком случае можно получить рута более простым способом -- так как мы и так уже получили привилегии администратора. Довольно нечестный прием. :) [Дальше]
четверг, 9 апреля 2009 г.
Применение специальных возможностей GCC в яде Linux
На www.ibm.com появилась статья, в чем-то перекликающаяся с моими постами:http://syslogblog.blogspot.com/2008/10/blog-post.html и http://syslogblog.blogspot.com/2009/01/gcc.html. Некоторые вещи, которые показались интересными:
Определение адреса, откуда была вызвана функция. level -- глубина стека.
Предварительная выборка.
[Дальше]
Определение адреса, откуда была вызвана функция. level -- глубина стека.
void * __builtin_return_address( unsigned int level );
Предварительная выборка.
void __builtin_prefetch( const void *addr, int rw, int locality );
[Дальше]
четверг, 19 марта 2009 г.
Слежение за дисковыми операциями
Иногда бывает полезно узнать, что за процессы пишут/читают с диска. Для этого можно воспользоваться механизмом отладки блокового ввода/вывода:
echo "1" > /proc/sys/vm/block_dump
И смотреть в dmesg: dmesg -c. При этом желательно остановить syslog, так как запись в syslog будет появляться в dmesg вызывая цепную реакцию. :) [Дальше]
echo "1" > /proc/sys/vm/block_dump
И смотреть в dmesg: dmesg -c. При этом желательно остановить syslog, так как запись в syslog будет появляться в dmesg вызывая цепную реакцию. :) [Дальше]
четверг, 15 января 2009 г.
Разочарования...
Что-то все больше разочарований. Сначала Debian, со своей параноидальной борьбой за чистоту рядов и бюрократией... А теперь вот, заглянул в ядро Ubuntu и что вижу?
Все патчи свалены в один файл. Причем один из патчей -- это совершенно не нужный мне AppArmor, затрагивающий vfs слой (то есть фактически, ломающий совместимость для потенциально интересных патчей). Раскрутить это монолитное безобразие очень тяжело и вряд-ли оправданно. В общем, делаю вывод, что большие дистрибутивы обладают довольно существенным недостатком -- инертностью. Когда/если у меня появится новый ноутбук буду пробовать ArchLinux и DragonFly BSD. Про rpm-based дистрибутивы речь, конечно, даже не идет -- это уже просто куча хлама. [Дальше]
Все патчи свалены в один файл. Причем один из патчей -- это совершенно не нужный мне AppArmor, затрагивающий vfs слой (то есть фактически, ломающий совместимость для потенциально интересных патчей). Раскрутить это монолитное безобразие очень тяжело и вряд-ли оправданно. В общем, делаю вывод, что большие дистрибутивы обладают довольно существенным недостатком -- инертностью. Когда/если у меня появится новый ноутбук буду пробовать ArchLinux и DragonFly BSD. Про rpm-based дистрибутивы речь, конечно, даже не идет -- это уже просто куча хлама. [Дальше]
вторник, 9 декабря 2008 г.
Что такое derived work или в чем несвобода gpl?
В тексте лицензии GPLv2 используется такое понятие как "derived". Если говорить конкретно, то речь идет о том, что если ваша программа есть порожденная работа от GPLv2 проекта, то и она должна быть выпущена под GPLv2. Но что считать порожденной работой?
Например, если я пишу игру на SDL, является ли код игры derived? Или у нас есть драйвер модема для Linux -- должен ли он быть открыт? Вопрос почти философский, и тем не менее (или -- тем более) требующий выяснения.
Наверное, самое простое и удачное правило (Linux: EXPORT_SYMBOL_GPL vs EXPORT_SYMBOL), выглядит так:
"if work A would not exist (or would be radically different) if work B did not exist, then A is a derivative work of B."
По-моему очень точно соответствует действительности. Драйвер устройства может быть в общем случае написан для любой системы. По крайней мере большая часть драйвера может быть независима от API ядра. В тоже время модуль к оконному менеджеру выпущенному под лицензией GPLv2, даже выделенный в отдельный проект -- должен быть выпущен под той-же лицензией.
Довольно давно, в коде ядра Linux некоторые из символов стали экспортироваться как EXPORT_SYMBOL_GPL, что означает возможность их использования только в GPL модулях. Похоже, такую идею можно назвать попыткой решить вопросы лицензионной политики программным способом. (Если используешь функцию/подсистему специфичную для ядра Linux -- то ты работаешь над GPL проектом.)
Вообще, интересно отметить, что здесь лицензия GPL оказывается не "просто свободной" лицензией (как часто привычно думается), а довольно прагматичным способом решить те задачи, которые поставил Столлман. Гильдия программистов -- пользователей лицензии GPLv2, имеют полную свободу модифицировать код проекта, но платят за это своей работой и что самое интересное, НЕсвободой освободить проект из под GPL лицензии и сделать его общественным достоянием в полном смысле, например, выпустив его под не-copyleft BSD-лицензией.
В тоже время выпустив проект под BSD-лицензией, мы можем получить GPL продукт на его основе, в следствии совместимости BSD с GPL.
Если вас также, как и меня, заинтересовали подобные вопросы, то рекомендую ознакомиться со статьей: Владимир Осинцев. Всегда ли General Public License – это свобода?
[Дальше]
Например, если я пишу игру на SDL, является ли код игры derived? Или у нас есть драйвер модема для Linux -- должен ли он быть открыт? Вопрос почти философский, и тем не менее (или -- тем более) требующий выяснения.
Наверное, самое простое и удачное правило (Linux: EXPORT_SYMBOL_GPL vs EXPORT_SYMBOL), выглядит так:
"if work A would not exist (or would be radically different) if work B did not exist, then A is a derivative work of B."
По-моему очень точно соответствует действительности. Драйвер устройства может быть в общем случае написан для любой системы. По крайней мере большая часть драйвера может быть независима от API ядра. В тоже время модуль к оконному менеджеру выпущенному под лицензией GPLv2, даже выделенный в отдельный проект -- должен быть выпущен под той-же лицензией.
Довольно давно, в коде ядра Linux некоторые из символов стали экспортироваться как EXPORT_SYMBOL_GPL, что означает возможность их использования только в GPL модулях. Похоже, такую идею можно назвать попыткой решить вопросы лицензионной политики программным способом. (Если используешь функцию/подсистему специфичную для ядра Linux -- то ты работаешь над GPL проектом.)
Вообще, интересно отметить, что здесь лицензия GPL оказывается не "просто свободной" лицензией (как часто привычно думается), а довольно прагматичным способом решить те задачи, которые поставил Столлман. Гильдия программистов -- пользователей лицензии GPLv2, имеют полную свободу модифицировать код проекта, но платят за это своей работой и что самое интересное, НЕсвободой освободить проект из под GPL лицензии и сделать его общественным достоянием в полном смысле, например, выпустив его под не-copyleft BSD-лицензией.
В тоже время выпустив проект под BSD-лицензией, мы можем получить GPL продукт на его основе, в следствии совместимости BSD с GPL.
Если вас также, как и меня, заинтересовали подобные вопросы, то рекомендую ознакомиться со статьей: Владимир Осинцев. Всегда ли General Public License – это свобода?
[Дальше]
суббота, 22 ноября 2008 г.
НЕ-модули безопасности (lsm)
Странная судьба у хорошего, казалось-бы, начинания - lsm. Моя работа связана с модификацией ядра и я, когда перешли к использованию ядра 2.6.xx, был очень рад использовать хуки lsm там, где это возможно. Потом, с каждой версией я с ужасом наблюдал как колбасится интерфейс lsm. Как уходят из него все больше и больше нужных кухов (например post_mknod нет, а socket_post_create есть) и как мечется мысль разработчиков в поисках "правильного" пути. А потом, потом они сказали что модули больше не модули. security: Convert LSM into a static interface. А потом убрали возможность дергать security функции из модулей. В общем, похоже теперь lsm годен только для SELinux и немногочисленных модулей в составе ядра... :(
[Дальше]
Неизвестная известная snprintf
Довольно часто при работе с форматными строками на C используется функция snprintf. При этом, типичной является конструкция, например, следующего вида:
Постепенно заполняем буфер форматированной строкой. При этом часто негласно принимается такое предположение, что функция вернет количество записанных в буфер байт (не считая последнего 0). Интересно, что это не всегда так.
Дело в том, что если почитать man по snprintf, то мы обнаружим, что в случае если размер буфера не достаточен для строки, то функция возвращает количество байт без последнего 0, которые БЫЛИ БЫ записаны, в случае, если БЫ размер буфера БЫЛ БЫ достаточен! То есть, если в приведенном выше примере произойдет отсечение результата, то на следующей итерации цикла мы полезем за границу буфера с отрицательным размером буфера (тип которого приведется к беззнаковому size_t)!
Интересно, что в реализации ядра Linux, функция vsnprintf содержит такую проверку:
То-есть, если в результате итераций в первом примере мы получаем отрицательный размер буфера, мы об этом узнаем... ;)
Кроме того, в ядре Linux есть реализация функции scnprintf, которая выглядит следующим образом:
Как видим, это та функция, которая всегда возвращает число записанных в буфер байт не считая 0 и -1 если размер буфера 0. Хотя в документации написано: "If size is <= 0 the function returns 0", и это мне не понятно, так как из кода следует, что все-таки это будет -1.
А вот другой пример контроля за размером буфера:
Правда типы sz и cnt в данном случае не size_t, а int, что не очень красиво.
Ну и, наконец, можно просто проверять накопленное смещение:
Приятной особенностью поведения функции snprintf является тот факт, что мы можем вычислять размер буфера для отформатированной строки за счет передачи нулевого размера буфера. Например:
Ссылки: snprintf() confusion' 2004 by corbet
[Дальше]
for (...) {
offset += snprintf(buf + offset, PAGE_SIZE - offset, " %02x", val);
...
Постепенно заполняем буфер форматированной строкой. При этом часто негласно принимается такое предположение, что функция вернет количество записанных в буфер байт (не считая последнего 0). Интересно, что это не всегда так.
Дело в том, что если почитать man по snprintf, то мы обнаружим, что в случае если размер буфера не достаточен для строки, то функция возвращает количество байт без последнего 0, которые БЫЛИ БЫ записаны, в случае, если БЫ размер буфера БЫЛ БЫ достаточен! То есть, если в приведенном выше примере произойдет отсечение результата, то на следующей итерации цикла мы полезем за границу буфера с отрицательным размером буфера (тип которого приведется к беззнаковому size_t)!
Интересно, что в реализации ядра Linux, функция vsnprintf содержит такую проверку:
if (unlikely((int) size < 0)) {
/* There can be only one.. */
static char warn = 1;
WARN_ON(warn);
warn = 0;
return 0;
}
То-есть, если в результате итераций в первом примере мы получаем отрицательный размер буфера, мы об этом узнаем... ;)
Кроме того, в ядре Linux есть реализация функции scnprintf, которая выглядит следующим образом:
int scnprintf(char * buf, size_t size, const char *fmt, ...)
{
va_list args;
int i;
va_start(args, fmt);
i = vsnprintf(buf, size, fmt, args);
va_end(args);
return (i >= size) ? (size - 1) : i;
}
Как видим, это та функция, которая всегда возвращает число записанных в буфер байт не считая 0 и -1 если размер буфера 0. Хотя в документации написано: "If size is <= 0 the function returns 0", и это мне не понятно, так как из кода следует, что все-таки это будет -1.
А вот другой пример контроля за размером буфера:
for (i = 0; i < npids; i++)
cnt += snprintf(buf + cnt, max(sz - cnt, 0), "%d\n", a[i]);
Правда типы sz и cnt в данном случае не size_t, а int, что не очень красиво.
Ну и, наконец, можно просто проверять накопленное смещение:
if ((offset += snprintf(...)) >= buffsize) {
/* ... error handling ... */
}
Приятной особенностью поведения функции snprintf является тот факт, что мы можем вычислять размер буфера для отформатированной строки за счет передачи нулевого размера буфера. Например:
sz = snprintf(NULL, 0, " ..... ", .... ) + 1;
ptr = malloc(sz);
snprintf(ptr, sz, " .... ", ... );
Ссылки: snprintf() confusion' 2004 by corbet
[Дальше]
суббота, 4 октября 2008 г.
Трюки в коде ядра Linux.
В коде ядра встречаются конструкции, которые можно назвать трюками программирования. Многие из них оказываются полезными, чтобы их запомнить и использовать.
Двойное отрицание
Иногда можно встретить двойное логическое отрицание. Например:
На самом деле, тут мы еще видим реализацию одного из способов оптимизации, но об этом в другой раз. Пример содержит двойное отрицание. Зачем?
Если x является логическим выражением, то двойное отрицание не нужно, но что будет если в коде мы напишем что-то вроде:
И тут становится понятно, что двойное отрицание выражения всегда дает булевую величину (0 или 1). Его можно назвать приведением к булевому типу, что довольно непривычно звучит в C.
Выравнивание
Ну, тут все понятно.
Комментарии с помощью препроцессора
Как видим, удобно для быстрого отключения/включения кода, особенно если внутри уже есть комментарии /* ... */.
Необычное сравнение с константой
Помогает избежать ошибок, когда вместо сравнения (==) по ошибке ставится знак присваивания (=).
Является ли значение степенью 2?
do { /*...*/ } while(0)
Очень часто используется при определении макросов, широко известный трюк. Теперь раскрытие макроса может использоваться как вызов функции, так как при раскрытии код будет полностью размещен в своем блоке, который выполнится один раз (while(0)). Внутри блока можно определять переменные. Например:
Использование goto
Можно долго говорить, что использование goto в C это плохо, но никакое правило не может быть универсальным. Иначе -- фарисейство или фанатизм... ;)
В большинстве случаев это попытка избежать большей вложенности кода при отработке ошибочных систуаций и освобождении ресурсов. Например:
Это гораздо проще, чем вложенные друг в друга блоки или излишнее разбиение на функции.
Возвращение кодов ошибок с типом "указатель".
Пример кода:
Здесь видно, что getname возвращает указатель, который тем-не менее, может содержать признак ошибки. Здесь, кстати, нужно быть осторожным! Так как это противоречит практике возвращать признак ошибки как NULL. Нужно всегда четко понимать, что возвращает функция ядра в случае ошибки: NULL или PTR_ERR. Например, если написать следующий код:
Это уже уязвимость, хотя выглядит прилично. Если не знать что возвращает getname...
Реализация механизма крайне проста, и сама по-себе тянет на трюк.
Как видим, указатель содержит отрицательное значение ошибки. Таким образом, выброшенный диапазон адресов приходится на последние 4Kb виртуального адресного пространства, который функция никогда не вернет. Очевидно, что превращение кода ошибки в указатель -- это приведение типа:
Вообще, все это дело терпимо для кода ядра Linux, но совсем нехорошо заниматься такими трюками в пользовательском (предположительно переносимом) коде, так-как все-таки это предполагает, что приведение long в void* и назад -- обратимы.
Вот и все на сегодня. :)
[Дальше]
Двойное отрицание
Иногда можно встретить двойное логическое отрицание. Например:
#define likely(x) __builtin_expect(!!(x), 1)
На самом деле, тут мы еще видим реализацию одного из способов оптимизации, но об этом в другой раз. Пример содержит двойное отрицание. Зачем?
Если x является логическим выражением, то двойное отрицание не нужно, но что будет если в коде мы напишем что-то вроде:
int counter=0x100;
/* ... */
if (likely(counter))
И тут становится понятно, что двойное отрицание выражения всегда дает булевую величину (0 или 1). Его можно назвать приведением к булевому типу, что довольно непривычно звучит в C.
Выравнивание
/* align addr on a size boundary - adjust address up/down if needed */
#define _ALIGN_UP(addr,size) (((addr)+((size)-1))&(~((size)-1)))
#define _ALIGN_DOWN(addr,size) ((addr)&(~((size)-1)))
Ну, тут все понятно.
Комментарии с помощью препроцессора
#if 0
/* XXX: let's do this when we verify it is OK */
if (ret & VM_FAULT_OOM)
ret = -ENOMEM;
#endif
Как видим, удобно для быстрого отключения/включения кода, особенно если внутри уже есть комментарии /* ... */.
Необычное сравнение с константой
if (NULL == siocb->scm)
siocb->scm = &tmp_scm;
Помогает избежать ошибок, когда вместо сравнения (==) по ошибке ставится знак присваивания (=).
Является ли значение степенью 2?
bool is_power_of_2(unsigned long n)
{
return (n != 0 && ((n & (n - 1)) == 0));
}
do { /*...*/ } while(0)
# define cap_clear(c) do { (c) = __cap_empty_set; } while (0)
Очень часто используется при определении макросов, широко известный трюк. Теперь раскрытие макроса может использоваться как вызов функции, так как при раскрытии код будет полностью размещен в своем блоке, который выполнится один раз (while(0)). Внутри блока можно определять переменные. Например:
#ifndef swap
#define swap(x, y) do { typeof(x) z = x; x = y; y = z; } while (0)
#endif
Использование goto
Можно долго говорить, что использование goto в C это плохо, но никакое правило не может быть универсальным. Иначе -- фарисейство или фанатизм... ;)
grep "goto" -R * | wc -lв дереве исходников ядра дает результат больший чем 50 тысяч.
В большинстве случаев это попытка избежать большей вложенности кода при отработке ошибочных систуаций и освобождении ресурсов. Например:
static long do_sys_truncate(const char __user * path, loff_t length)
{
struct nameidata nd;
struct inode * inode;
int error;
error = -EINVAL;
if (length < 0) /* sorry, but loff_t says... */
goto out;
/* ... */
inode = nd.path.dentry->d_inode;
/* For directories it's -EISDIR, for other non-regulars - -EINVAL */
error = -EISDIR;
if (S_ISDIR(inode->i_mode))
goto dput_and_out;
/* ... */
error = vfs_permission(&nd, MAY_WRITE);
if (error)
goto mnt_drop_write_and_out;
/* ... */
put_write_and_out:
put_write_access(inode);
mnt_drop_write_and_out:
mnt_drop_write(nd.path.mnt);
dput_and_out:
path_put(&nd.path);
out:
return error;
}
Это гораздо проще, чем вложенные друг в друга блоки или излишнее разбиение на функции.
Возвращение кодов ошибок с типом "указатель".
Пример кода:
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
/* ... */
}
return fd;
}
Здесь видно, что getname возвращает указатель, который тем-не менее, может содержать признак ошибки. Здесь, кстати, нужно быть осторожным! Так как это противоречит практике возвращать признак ошибки как NULL. Нужно всегда четко понимать, что возвращает функция ядра в случае ошибки: NULL или PTR_ERR. Например, если написать следующий код:
char *tmp = getname(filename);
if (!tmp)
return NULL;
/* ... do something .. */
Это уже уязвимость, хотя выглядит прилично. Если не знать что возвращает getname...
Реализация механизма крайне проста, и сама по-себе тянет на трюк.
#define MAX_ERRNO 4095
#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
static inline long IS_ERR(const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);
}
Как видим, указатель содержит отрицательное значение ошибки. Таким образом, выброшенный диапазон адресов приходится на последние 4Kb виртуального адресного пространства, который функция никогда не вернет. Очевидно, что превращение кода ошибки в указатель -- это приведение типа:
static inline void *ERR_PTR(long error)
{
return (void *) error;
}
Вообще, все это дело терпимо для кода ядра Linux, но совсем нехорошо заниматься такими трюками в пользовательском (предположительно переносимом) коде, так-как все-таки это предполагает, что приведение long в void* и назад -- обратимы.
Вот и все на сегодня. :)
[Дальше]
Компиляция выбранного модуля в дереве сборки ядра Linux 2.6.
make -c <KERNEL TREE> SUBDIRS=<MODULES RELATIVE PATH> modules
Например. Весь каталог:
Выбранный модуль:
[Дальше]
Например. Весь каталог:
make -C /usr/src/linux-source-2.6.26 SUBDIRS=drivers/macintosh modules
Выбранный модуль:
make -C /usr/src/linux-source-2.6.26 SUBDIRS=drivers/macintosh therm_adt746x.koМинимальный Makefile своего модуля:
obj-m := our_module.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
[Дальше]
Ярлыки:
kernel
Подписаться на:
Сообщения (Atom)