На данный момент облачные CI-системы — очень востребованный сервис. В этой статье мы расскажем, как, с помощью уже существующих средств, доступных в PVS-Studio, можно интегрировать анализ исходного кода с облачной CI платформой, на примере сервиса Travis CI.

Picture 1


Почему мы рассматриваем сторонние облака и не делаем собственное? Есть ряд причин, и главная из них, что организация 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
Примечание: Разработчики LXC уже используют Travis CI, поэтому мы возьмем их конфигурационный файл в качестве основы и отредактируем его для наших целей.

Настройка


Для начала работы с Travis CI переходим по ссылке и аутентифицируемся, используя GitHub-аккаунт.

Picture 17

В открывшемся окне нужно авторизовать Travis CI.

Picture 16

После авторизации происходит перенаправление на приветственную страницу«First time here? Lets get you started!», где кратко описано, что необходимо дальше сделать для начала работы:

  • активировать репозитории;
  • добавить файл .travis.yml в репозиторий;
  • запустить первую сборку.

Picture 18

Начнем выполнять эти пункты.

Для добавления в Travis CI нашего репозитория переходим в настройки профиля по ссылке и нажимаем кнопку «Activate».

Picture 19

После нажатия откроется окно с выбором репозиториев, к которым приложению Travis CI будет предоставлен доступ.
Примечание: для предоставления доступа к репозиторию у учетной записи должны быть права администратора на него.

Picture 38

Выбираем нужный репозиторий, подтверждаем выбор кнопкой «Approve & Install», и нас перенаправит обратно на страницу настройки профиля.

Сразу создадим переменные, которые будем использовать для создания файла лицензии анализатора и отсылки его отчетов. Для этого перейдем на страницу настроек — кнопка «Settings» справа от нужного репозитория.

Picture 39

Откроется окно настроек.

Picture 41

Краткое описание настроек:

  • Секция «General» – настройка триггеров автозапуска задачи;
  • Секция «Auto Cancelation» – позволяет настроить автоотмену сборки;
  • Секция «Environment Variables» – позволяет определить переменные окружения, содержащие как открытую, так и конфиденциальную информацию, такие как учетные данные, ssh-ключи;
  • Секция «Cron Jobs» – настройка расписания запуска задачи.

В секции «Environment Variables» создадим переменные PVS_USERNAME и PVS_KEY, содержащие, соответственно, имя пользователя и лицензионный ключ для статического анализатора. Если у вас нет постоянной лицензии PVS-Studio, то вы можете запросить триальную лицензию.

Picture 5

Тут же создадим переменные MAIL_USER и MAIL_PASSWORD, содержащие имя пользователя и пароль от почтового ящика, который мы будем использовать для отсылки отчетов.

Picture 4

При запуске задачи 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 получил уведомление о наличии изменений в проекте и автоматически запустил сборку.

Подробную информацию о ходе сборки и проверке анализатором можно увидеть в консоли.

Picture 2

После окончания тестов мы получим на почту 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;

Picture 3


Правильный вариант кода:

*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)


  1. gecube
    28.06.2019 15:38
    +1

    За образ с докером — жирный плюс.
    Молодцы, что обратили внимание на интеграцию с CI процессами, в частности, на облачных платформах! Уверен, что это поспособствует популярности вашего продукта.


  1. dion
    28.06.2019 22:44
    +2

     - wget -q -O - http://files.viva64.com/etc/pubkey.txt | sudo apt-key add -

    Здравствуй MITM… Вы бы хоть https какой-то прикрутили, если уж такое советуете...


    1. hell0w0rd
      29.06.2019 12:14
      -2

      https не спасает от mitm


      1. gecube
        29.06.2019 22:58

        Ну, я соглашусь и не соглашусь.
        В случае докера — скорее всего возьмёте образ от вендора. В нем проблемы явно не будет, т.к. либо его собрали нормально, либо должны пересобрать.
        Если собирать самому — да, лучше, если ключи apt зашиты как переменные внутри докерфайла, это гибче и надёжнее. Та же история с контентом, который выкачивают из инета (надо sha суммы класть в образ).
        Не стоит забывать, что докерфайл — это всего лишь чертеж, а не само "изделие", полученное по нему


        1. hell0w0rd
          29.06.2019 23:06

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


      1. hell0w0rd
        29.06.2019 23:08
        -1

        Неужели минусующие защищаются от mitm, заменяя http на https в скриптах?


      1. dion
        29.06.2019 23:43
        +1

        Конкретно в этом случае спасает.
        Поясняю: сейчас банальная подмена DNS для files.viva64.com или перехват траффика позволяет засунуть в apt keyring жертвы сгенерированый злоумышлеником ключ. Ну и дальше можно делать что хочешь, например предложить обновить любой пакет в системе, просто подписав левый репозиторий этим ключем.


        Если бы тут был https, то эта атака загнулась бы еще на скачивании ключа. Понятно что владельцы files.viva64.com могут подсунуть левый пакет, но это заметно лучше чем когда это делает вообще кто попало.


    1. OAndreev Автор
      01.07.2019 10:15
      +3

      Поправил ссылки на https.


  1. vt4a2h
    28.06.2019 23:53

    Отличные новости, спасибо!

    Теперь ждём веб-сервис для работы с результатами анализа. Ну это, по крайней мере, выглядит как логичное развитие продукта, раз уж вы «пошли в облака» :)


    1. EvgeniyRyzhkov
      29.06.2019 07:07
      +1

      Зачем его ждать, если уже давно у нас есть решения для этого:

      • Всегда достуна интеграция результатов анализа PVS-Studio в SonarQube (url)
      • Либо с помощью утилиты PlogConverter можно сделать .html-отчет, включающий исходники (url)


      1. vt4a2h
        29.06.2019 15:36

        Про SonarQube не знал, спасибо.


  1. AndrewSu
    29.06.2019 18:24

    Спасибо за статью!
    Использую PVS-Studio в своём домашнем проекте уже полгода с интеграцией с travis-ci, после новогодней раздачи ключей для открытых проектов.
    У меня скрипт запуска получился очень похожий, но только заканчивается на

    test "$(cat ./pvs-error-list.txt | wc -l)" -le 1

    для того, чтобы не пропустить ни одной новой ошибки.