Почему мы рассматриваем сторонние облака и не делаем собственное? Есть ряд причин, и главная из них, что организация SaaS — это достаточно дорогая и непростая процедура. На самом деле, непосредственно интегрировать анализ PVS-Studio со сторонней облачной платформой (будь то открытые платформы наподобие CircleCI, Travis CI, GitLab, или какое-то специализированное enterprise-решение, используемое только в одной конкретной компании) – задача достаточно простая и тривиальная. То есть можно сказать, что PVS-Studio уже доступен «в облаках». Совсем другой вопрос в организации и предоставлении инфраструктуры для такой работы в режиме 24/7. Это задача совсем другого порядка, и PVS-Studio пока что не планирует предоставлять свою собственную облачную платформу непосредственно для запуска на ней анализа.
Информация об используемом ПО
Travis CI – сервис для сборки и тестирования программного обеспечения, использующего GitHub в качестве хранилища. Travis CI не требует изменения программного кода для использования сервиса, все настройки происходят в файле .travis.yml, расположенном в корне репозитория.
В качестве тестового проекта для проверки с помощью PVS-Studio мы возьмем LXC (Linux Containers). Это система виртуализации на уровне операционной системы для запуска нескольких экземпляров операционной системы Linux на одном узле.
Проект маленький, но для демонстрации более чем достаточен. Вывод команды cloc:
Language |
files |
blank |
comment |
code |
C |
124 |
11937 |
6758 |
50836 |
C/C++ Header |
65 |
1117 |
3676 |
3774 |
Настройка
Для начала работы с Travis CI переходим по ссылке и аутентифицируемся, используя GitHub-аккаунт.
В открывшемся окне нужно авторизовать Travis CI.
После авторизации происходит перенаправление на приветственную страницу«First time here? Lets get you started!», где кратко описано, что необходимо дальше сделать для начала работы:
- активировать репозитории;
- добавить файл .travis.yml в репозиторий;
- запустить первую сборку.
Начнем выполнять эти пункты.
Для добавления в Travis CI нашего репозитория переходим в настройки профиля по ссылке и нажимаем кнопку «Activate».
После нажатия откроется окно с выбором репозиториев, к которым приложению Travis CI будет предоставлен доступ.
Примечание: для предоставления доступа к репозиторию у учетной записи должны быть права администратора на него.
Выбираем нужный репозиторий, подтверждаем выбор кнопкой «Approve & Install», и нас перенаправит обратно на страницу настройки профиля.
Сразу создадим переменные, которые будем использовать для создания файла лицензии анализатора и отсылки его отчетов. Для этого перейдем на страницу настроек — кнопка «Settings» справа от нужного репозитория.
Откроется окно настроек.
Краткое описание настроек:
- Секция «General» – настройка триггеров автозапуска задачи;
- Секция «Auto Cancelation» – позволяет настроить автоотмену сборки;
- Секция «Environment Variables» – позволяет определить переменные окружения, содержащие как открытую, так и конфиденциальную информацию, такие как учетные данные, ssh-ключи;
- Секция «Cron Jobs» – настройка расписания запуска задачи.
В секции «Environment Variables» создадим переменные PVS_USERNAME и PVS_KEY, содержащие, соответственно, имя пользователя и лицензионный ключ для статического анализатора. Если у вас нет постоянной лицензии PVS-Studio, то вы можете запросить триальную лицензию.
Тут же создадим переменные MAIL_USER и MAIL_PASSWORD, содержащие имя пользователя и пароль от почтового ящика, который мы будем использовать для отсылки отчетов.
При запуске задачи Travis CI берет инструкции из файла .travis.yml, лежащего в корне репозитория.
Используя Travis CI, мы можем запускать статический анализ как непосредственно в виртуальной машине, так и использовать для этого предварительно настроенный контейнер. Результаты этих подходов ничем не отличаются друг от друга, но использование предварительно настроенного контейнера может пригодиться, например, если у нас уже есть контейнер с каким-то специфическим окружением, внутри которого собирается и тестируется программный продукт, и нет желания восстанавливать это окружение в Travis CI.
Создадим конфигурацию для запуска анализатора в виртуальной машине.
Для сборки и тестирования мы будем использовать виртуальную машину на базе Ubuntu Trusty, ее описание можно посмотреть по ссылке.
Первым делом указываем, что проект написан на С и перечисляем компиляторы, которые будем использовать для сборки:
language: c
compiler:
- gcc
- clang
Примечание: при указании более одного компилятора, задачи будут запускаться параллельно для каждого из них. Подробнее можно прочитать в документации.
Перед началом сборки нам необходимо добавить репозиторий анализатора, установить зависимости и дополнительные пакеты:
before_install:
- sudo add-apt-repository ppa:ubuntu-lxc/daily -y
- wget -q -O - https://files.viva64.com/etc/pubkey.txt | sudo apt-key add -
- sudo wget -O /etc/apt/sources.list.d/viva64.list
https://files.viva64.com/etc/viva64.list
- sudo apt-get update -qq
- sudo apt-get install -qq coccinelle parallel
libapparmor-dev libcap-dev libseccomp-dev
python3-dev python3-setuptools docbook2x
libgnutls-dev libselinux1-dev linux-libc-dev pvs-studio
libio-socket-ssl-perl libnet-ssleay-perl sendemail
ca-certificates
Перед сборкой проекта необходимо подготовить окружение:
script:
- ./coccinelle/run-coccinelle.sh -i
- git diff --exit-code
- export CFLAGS="-Wall -Werror"
- export LDFLAGS="-pthread -lpthread"
- ./autogen.sh
- rm -Rf build
- mkdir build
- cd build
- ../configure --enable-tests --with-distro=unknown
Далее нам необходимо создать файл с лицензией и запустить анализ проекта.
Первой командой создаем файл с лицензией для анализатора. Данные для переменных $PVS_USERNAME и $PVS_KEY берутся из настроек проекта.
- pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
Следующей командой запускаем трассировку сборки проекта:
- pvs-studio-analyzer trace -- make -j4
После запускаем статический анализ.
Примечание: при использовании триальной лицензии необходимо указывать параметр --disableLicenseExpirationCheck.
- pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic
-o PVS-Studio-${CC}.log
–-disableLicenseExpirationCheck
Последней командой файл с результатами работы анализатора конвертируется в html-отчет.
- plog-converter -t html PVS-Studio-${CC}.log
-o PVS-Studio-${CC}.html
Так как TravisCI не позволяет изменять формат почтовых уведомлений, то для отсылки отчетов на последнем шаге воспользуемся пакетом sendemail:
- sendemail -t mail@domain.com
-u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT"
-m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT"
-s smtp.gmail.com:587
-xu $MAIL_USER
-xp $MAIL_PASSWORD
-o tls=yes
-f $MAIL_USER
-a PVS-Studio-${CC}.log PVS-Studio-${CC}.html
Полный текст конфигурационного файла для запуска анализатора в виртуальной машине:
language: c
compiler:
- gcc
- clang
before_install:
- sudo add-apt-repository ppa:ubuntu-lxc/daily -y
- wget -q -O - https://files.viva64.com/etc/pubkey.txt | sudo apt-key add -
- sudo wget -O /etc/apt/sources.list.d/viva64.list
https://files.viva64.com/etc/viva64.list
- sudo apt-get update -qq
- sudo apt-get install -qq coccinelle parallel
libapparmor-dev libcap-dev libseccomp-dev
python3-dev python3-setuptools docbook2x
libgnutls-dev libselinux1-dev linux-libc-dev pvs-studio
libio-socket-ssl-perl libnet-ssleay-perl sendemail
ca-certificates
script:
- ./coccinelle/run-coccinelle.sh -i
- git diff --exit-code
- export CFLAGS="-Wall -Werror"
- export LDFLAGS="-pthread -lpthread"
- ./autogen.sh
- rm -Rf build
- mkdir build
- cd build
- ../configure --enable-tests --with-distro=unknown
- pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
- pvs-studio-analyzer trace -- make -j4
- pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic
-o PVS-Studio-${CC}.log
--disableLicenseExpirationCheck
- plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html
- sendemail -t mail@domain.com
-u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT"
-m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT"
-s smtp.gmail.com:587
-xu $MAIL_USER
-xp $MAIL_PASSWORD
-o tls=yes
-f $MAIL_USER
-a PVS-Studio-${CC}.log PVS-Studio-${CC}.html
Для запуска статического анализатора в контейнере, предварительно создадим его, используя следующий Dockerfile:
FROM docker.io/ubuntu:trusty
ENV CFLAGS="-Wall -Werror"
ENV LDFLAGS="-pthread -lpthread"
RUN apt-get update && apt-get install -y software-properties-common wget && wget -q -O - https://files.viva64.com/etc/pubkey.txt |
sudo apt-key add - && wget -O /etc/apt/sources.list.d/viva64.list
https://files.viva64.com/etc/viva64.list && apt-get update && apt-get install -yqq coccinelle parallel
libapparmor-dev libcap-dev libseccomp-dev
python3-dev python3-setuptools docbook2x
libgnutls-dev libselinux1-dev linux-libc-dev
pvs-studio git libtool autotools-dev automake
pkg-config clang make libio-socket-ssl-perl
libnet-ssleay-perl sendemail ca-certificates && rm -rf /var/lib/apt/lists/*
В этом случае конфигурационный файл может выглядеть так:
before_install:
- docker pull docker.io/oandreev/lxc
env:
- CC=gcc
- CC=clang
script:
- docker run
--rm
--cap-add SYS_PTRACE
-v $(pwd):/pvs
-w /pvs
docker.io/oandreev/lxc
/bin/bash -c " ./coccinelle/run-coccinelle.sh -i
&& git diff --exit-code
&& ./autogen.sh
&& mkdir build && cd build
&& ../configure CC=$CC
&& pvs-studio-analyzer credentials
$PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
&& pvs-studio-analyzer trace -- make -j4
&& pvs-studio-analyzer analyze -j2
-l PVS-Studio.lic
-o PVS-Studio-$CC.log
--disableLicenseExpirationCheck
&& plog-converter -t html
-o PVS-Studio-$CC.html
PVS-Studio-$CC.log
&& sendemail -t mail@domain.com
-u 'PVS-Studio $CC report, commit:$TRAVIS_COMMIT'
-m 'PVS-Studio $CC report, commit:$TRAVIS_COMMIT'
-s smtp.gmail.com:587
-xu $MAIL_USER -xp $MAIL_PASSWORD
-o tls=yes -f $MAIL_USER
-a PVS-Studio-${CC}.log PVS-Studio-${CC}.html"
Как можно увидеть, в данном случае мы ничего не делаем внутри виртуальной машины, и абсолютно все действия по сборке и тестированию проекта происходят внутри контейнера.
Примечание: при запуске контейнера необходимо указывать параметр --cap-add SYS_PTRACE либо --security-opt seccomp:unconfined, так как для трассировки компиляции используется системный вызов ptrace.
Загружаем конфигурационный файл в корень репозитория и видим, что Travis CI получил уведомление о наличии изменений в проекте и автоматически запустил сборку.
Подробную информацию о ходе сборки и проверке анализатором можно увидеть в консоли.
После окончания тестов мы получим на почту 2 письма: одно – с результатами статического анализа для сборки проекта с использованием gcc, и второе – соответственно, clang.
Коротко про результаты проверки
В целом проект достаточно чистый, анализатор выдал всего 24 критических и 46 средних предупреждений. Для демонстрации работы рассмотрим пару интересных уведомлений:
Избыточные условия в if
V590 Consider inspecting the 'ret != (- 1) && ret == 1' expression. The expression is excessive or contains a misprint. attach.c 107
#define EOF -1
static struct lxc_proc_context_info *lxc_proc_get_context_info(pid_t pid)
{
....
while (getline(&line, &line_bufsz, proc_file) != -1)
{
ret = sscanf(line, "CapBnd: %llx", &info->capability_mask);
if (ret != EOF && ret == 1) // <=
{
found = true;
break;
}
}
....
}
Если ret == 1, то он точно не равен -1 (EOF). Избыточная проверка, можно убрать ret != EOF.
Таких предупреждений было выдано еще два:
- V590 Consider inspecting the 'ret != (- 1) && ret == 1' expression. The expression is excessive or contains a misprint. attach.c 579
- V590 Consider inspecting the 'ret != (- 1) && ret == 1' expression. The expression is excessive or contains a misprint. attach.c 583
Потеря старших битов
V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. conf.c 1879
struct mount_opt
{
char *name;
int clear;
int flag;
};
static void parse_mntopt(char *opt, unsigned long *flags,
char **data, size_t size)
{
struct mount_opt *mo;
/* If opt is found in mount_opt, set or clear flags.
* Otherwise append it to data. */
for (mo = &mount_opt[0]; mo->name != NULL; mo++)
{
if (strncmp(opt, mo->name, strlen(mo->name)) == 0)
{
if (mo->clear)
{
*flags &= ~mo->flag; // <=
}
else
{
*flags |= mo->flag;
}
return;
}
}
....
}
Под Linux'ом long — это 64-битная целочисленная переменная, mo->flag — 32-битная целочисленная переменная. Использование mo->flag в качестве битовой маски приведет к потере 32 старших бит. Выполняется неявное приведение битовой маски к 64-битной целочисленной переменной после побитовой инверсии. Старшие биты этой маски будут нулевыми.
Продемонстрируем на примере:
unsigned long long x;
unsigned y;
....
x &= ~y;
Правильный вариант кода:
*flags &= ~(unsigned long)(mo->flag);
Анализатор выдал еще одно подобное предупреждение:
- V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. conf.c 1933
Подозрительный цикл
V612 An unconditional 'return' within a loop. conf.c 3477
#define lxc_list_for_each(__iterator, __list) for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
static bool verify_start_hooks(struct lxc_conf *conf)
{
char path[PATH_MAX];
struct lxc_list *it;
lxc_list_for_each (it, &conf->hooks[LXCHOOK_START]) {
int ret;
char *hookname = it->elem;
ret = snprintf(path, PATH_MAX, "%s%s",
conf->rootfs.path ? conf->rootfs.mount : "",
hookname);
if (ret < 0 || ret >= PATH_MAX)
return false;
ret = access(path, X_OK);
if (ret < 0) {
SYSERROR("Start hook \"%s\" not found in container",
hookname);
return false;
}
return true; // <=
}
return true;
}
Запускают цикл и на первой итерации его прерывают. Возможно, так и задумывалось, но тогда цикл можно опустить.
Выход за границы массива
V557 Array underrun is possible. The value of 'bytes — 1' index could reach -1. network.c 2570
static int lxc_create_network_unpriv_exec(const char *lxcpath,
const char *lxcname,
struct lxc_netdev *netdev,
pid_t pid,
unsigned int hooks_version)
{
int bytes;
char buffer[PATH_MAX] = {0};
....
bytes = lxc_read_nointr(pipefd[0], &buffer, PATH_MAX);
if (bytes < 0)
{
SYSERROR("Failed to read from pipe file descriptor");
close(pipefd[0]);
}
else
{
buffer[bytes - 1] = '\0';
}
....
}
Из pipe'а читаются байты в буфер. В случае ошибки, функция lxc_read_nointr вернет отрицательное значение. Если все прошло успешно, то последним элементом записывают нуль-терминал. Однако, если будет прочитано 0 байт, то произойдет выход за границу буфера, что ведет к неопределенному поведению.
Анализатор выдал еще одно подобное предупреждение:
- V557 Array underrun is possible. The value of 'bytes — 1' index could reach -1. network.c 2725
Переполнение буфера
V576 Incorrect format. Consider checking the third actual argument of the 'sscanf' function. It's dangerous to use string specifier without width specification. Buffer overflow is possible. lxc_unshare.c 205
static bool lookup_user(const char *oparg, uid_t *uid)
{
char name[PATH_MAX];
....
if (sscanf(oparg, "%u", uid) < 1)
{
/* not a uid -- perhaps a username */
if (sscanf(oparg, "%s", name) < 1) // <=
{
free(buf);
return false;
}
....
}
....
}
Использование sscanf в данном случае может являться опасным, поскольку если длина буфера oparq окажется больше длины буфера name, произойдет выход за границу при формировании буфера name.
Заключение
Как мы увидели, настроить проверку статическим анализатором кода нашего проекта в облаке – достаточно простая задача. Для этого необходимо всего лишь добавить один файл в репозиторий и потратить минимальное время на настройку CI-системы. В результате же мы получим инструмент, позволяющий выявлять проблемный код на этапе написания, и не позволяющий ошибкам попадать на следующие этапы тестирования, где их исправление займет больше времени и ресурсов.
Конечно, использование PVS-Studio совместно с облачными платформами не ограничивается только Travis CI. По аналогии с описанным в статье способом, с минимальными отличиями, анализ PVS-Studio можно интегрировать и с другими популярными облачными CI решениями, такими, как CircleCI, GitLab и т.п.
Полезные ссылки
- Дополнительную информацию про запуск PVS-Studio в Linux и MacOS можно получить здесь.
- Про создание, настройку и использование контейнеров с установленным статическим анализатором PVS-Studio можно почитать здесь.
- Документация TravisCI.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Oleg Andreev. PVS-Studio in the Clouds -Running the Analysis on Travis CI
Комментарии (12)
dion
28.06.2019 22:44+2- wget -q -O - http://files.viva64.com/etc/pubkey.txt | sudo apt-key add -
Здравствуй MITM… Вы бы хоть https какой-то прикрутили, если уж такое советуете...
hell0w0rd
29.06.2019 12:14-2https не спасает от mitm
gecube
29.06.2019 22:58Ну, я соглашусь и не соглашусь.
В случае докера — скорее всего возьмёте образ от вендора. В нем проблемы явно не будет, т.к. либо его собрали нормально, либо должны пересобрать.
Если собирать самому — да, лучше, если ключи apt зашиты как переменные внутри докерфайла, это гибче и надёжнее. Та же история с контентом, который выкачивают из инета (надо sha суммы класть в образ).
Не стоит забывать, что докерфайл — это всего лишь чертеж, а не само "изделие", полученное по немуhell0w0rd
29.06.2019 23:06Не понимаю, как ваш комментарий касается mitm.
Тем не менее не стоит доверять даже образам от вендора, есть масса способов залить «исправленный» образ. Про чертеж и хешсумму полностью согласен.
dion
29.06.2019 23:43+1Конкретно в этом случае спасает.
Поясняю: сейчас банальная подмена DNS для files.viva64.com или перехват траффика позволяет засунуть в apt keyring жертвы сгенерированый злоумышлеником ключ. Ну и дальше можно делать что хочешь, например предложить обновить любой пакет в системе, просто подписав левый репозиторий этим ключем.
Если бы тут был https, то эта атака загнулась бы еще на скачивании ключа. Понятно что владельцы files.viva64.com могут подсунуть левый пакет, но это заметно лучше чем когда это делает вообще кто попало.
vt4a2h
28.06.2019 23:53Отличные новости, спасибо!
Теперь ждём веб-сервис для работы с результатами анализа. Ну это, по крайней мере, выглядит как логичное развитие продукта, раз уж вы «пошли в облака» :)
AndrewSu
29.06.2019 18:24Спасибо за статью!
Использую PVS-Studio в своём домашнем проекте уже полгода с интеграцией с travis-ci, после новогодней раздачи ключей для открытых проектов.
У меня скрипт запуска получился очень похожий, но только заканчивается на
test "$(cat ./pvs-error-list.txt | wc -l)" -le 1
для того, чтобы не пропустить ни одной новой ошибки.
gecube
За образ с докером — жирный плюс.
Молодцы, что обратили внимание на интеграцию с CI процессами, в частности, на облачных платформах! Уверен, что это поспособствует популярности вашего продукта.