Data race through standard library function call

Несколько задач делают незащищенные вызовы небезопасной для потока стандартной функции библиотеки

Описание

Этот дефект возникает, когда:

  1. Несколько задач вызывают одну и ту же стандартную функцию библиотеки.

    Например, несколько задач вызывают strerror функция.

  2. Вызовы не защищены с помощью общей защиты.

    Например, вызовы не защищены одной и той же критической секцией.

Функции, отмеченные этим дефектом, не гарантированы как входящие. Функция повторяется, если ее можно прервать и безопасно вызвать снова, прежде чем ее предыдущий вызов завершит выполнение. Если функция не является входящей, несколько задач, вызывающих функцию без защиты, могут вызвать проблемы параллелизма. Список функций, которые помечены, см. в CON33-C: Избегайте условий расы при использовании функций библиотеки.

Чтобы найти этот дефект, перед анализом необходимо задать опции многозадачности. Чтобы задать эти опции, на панели Configuration выберите Multitasking. Для получения дополнительной информации смотрите Настройка многозадачного анализа Polyspace вручную.

Риск

Функции, помеченные этим дефектом, не входят, потому что в их реализациях могут использоваться глобальные или статические переменные. Когда несколько задач вызывают функцию без защиты, вызов функции из одной задачи может помешать вызову из другой задачи. Два вызова функции могут одновременно получить доступ к глобальным или статическим переменным и вызвать непредсказуемые результаты.

Вызовы могут также вызвать более серьезные уязвимости безопасности, такие как ненормальное завершение, атака отказа в обслуживании и нарушения целостности данных.

Зафиксировать

Чтобы исправить этот дефект, выполните одно из следующих действий:

  • Используйте повторную версию стандартной функции библиотеки, если она существует.

    Для образца вместо strerror(), использовать strerror_r() или strerror_s(). Альтернативы функциям, отмеченным этим дефектом, см. в документации для CON33-C.

  • Защитите вызовы функций, используя общие критические разделы или временное исключение.

    См. Critical section details (-critical-section-begin -critical-section-end) и Temporally exclusive tasks (-temporal-exclusions-file).

    Чтобы идентифицировать существующие защиты, которые можно повторно использовать, смотрите таблицу и графики, связанные с результатом. В таблице показаны каждая пара конфликтующих вызовов. В Access Protections столбце показаны существующие средства защиты вызовов. Чтобы увидеть последовательность вызовов функций, ведущую к конфликтам, щелкните значок. Пример см. ниже.

Примеры

расширить все

#include <errno.h>
#include <stdio.h>
#include <string.h>

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

В этом примере, чтобы эмулировать поведение в многозадачном режиме, задайте следующие опции:

ОпцияСпецификация
Configure multitasking manually
Tasks (-entry-points)

task1

task2

task3

Critical section details (-critical-section-begin -critical-section-end)Starting routineEnding routine
begin_critical_sectionend_critical_section

В командной строке можно использовать следующее:

 polyspace-bug-finder
   -entry-points task1,task2,task3
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

В этом примере задачи, task1, task2 и task3, вызовите функцию func. func вызывает невходящую функцию стандартной библиотеки, strerror.

Хотя task3 вызывает func внутри критического раздела другие задачи не используют тот же критический раздел. Операции в критическом разделе task3 не являются взаимоисключающими с операциями в других задачах.

Эти три задачи вызывают не входящую стандартную функцию библиотеки без общей защиты. В подробных данных результата вы видите каждую пару конфликтующих вызовов функций.

При нажатии значка отображается последовательность вызовов функций, начиная с точки входа и заканчивая вызовом стандартной функции библиотеки. Вы также видите, что вызов начинается с task3 находится в критическом разделе. Запись Access Protections показывает функцию блокировки и разблокировки, которая начинает и заканчивает критический раздел. В этом примере вы видите функции begin_critical_section и end_critical_section.

Коррекция - Используйте Reentrant Version функции стандартной библиотеки

Одной из возможных коррекций является использование перевходной версии стандартной функции библиотеки strerror. Вы можете использовать POSIX® версия strerror_r который имеет ту же функциональность, но также гарантирует безопасность потоков.

#include <errno.h>
#include <stdio.h>
#include <string.h>

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);
enum { BUFFERSIZE = 64 };

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char errmsg[BUFFERSIZE];
    if (strerror_r(errno, errmsg, BUFFERSIZE) != 0) {
      /* Handle error */
    }
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}
Коррекция - поместите вызов функции в критический раздел

Одной из возможных коррекций является размещение вызова на strerror в критическом разделе. Можно реализовать критический раздел несколькими способами.

Например, можно поместить вызов на промежуточную функцию func в том же критическом разделе в трех задачах. Когда task1 входит в его критический раздел, другие задачи не могут войти в их критические разделы, пока task1 покидает его критический раздел. Область вызовов для func и поэтому вызовы в strerror из трех задач не могут мешать друг другу.

Для реализации критического раздела в каждой из трех задач вызовите func между вызовами в begin_critical_section и end_critical_section.

#include <errno.h>
#include <stdio.h>
#include <string.h>

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    begin_critical_section();
    func(fptr1);
    end_critical_section();
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     begin_critical_section();
     func(fptr2);
     end_critical_section();
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

Коррекция - Делайте задачи временно эксклюзивными

Другой возможной коррекцией является выполнение задач, task1, task2 и task3, временно эксклюзивный. Временные исключающие задачи не могут выполняться одновременно.

На панели Configuration задайте следующие дополнительные опции:

ОпцияЗначение
Temporally exclusive tasks (-temporal-exclusions-file)

task1 task2 task3

В командной строке можно использовать следующее:

 polyspace-bug-finder
     -temporal-exclusions-file "C:\exclusions_file.txt"
где файл C:\exclusions_file.txt имеет следующую линию:
task1 task2 task3

Информация о результатах

Группа: Параллелизм
Язык: C | C++
По умолчанию: On
Синтаксис командной строки: DATA_RACE_STD_LIB
Влияние: Высокий
ИДЕНТИФИКАТОР CWE: 366, 413
Введенный в R2016b