2016-11-11 2 views
0

Мне нравится код гольфа. В свете CVE-2016-5195 мне было интересно, может ли какой-либо из моих «играющих в гольф» код вызвать условия гонки.Может ли короткое замыкание рассчитать состояние гонки?

Допустим, у нас есть две функции, оба из которых возвращают логическое значение, и мы инициализируем булеву переменную result:

result = foo() || bar(); 

В идеальном мире, у нас есть два сценария:

  1. footrue. Do не позвонить bar. result соответствует true. [сценарий короткого замыкания]
  2. foofalse. Do звонок bar(). result соответствует какой bar возврат.

Мой вопрос: Будет ли когда-нибудь время, когда оценка короткого замыкания нарушается, и bar называется, несмотря на foo возвращение true, или даже хуже, bar вызывается перед foo называется, возможно, из-за многопоточности? Если да, можете ли вы предоставить фрагмент кода, который может вызвать такое поведение?

Ваш ответ может быть о любом языке (языках), на котором этот синтаксис действителен, хотя я полагаю, что некоторые языки будут более строгими в отношении такого рода вещей, чем другие.

+0

Возможно, это неявно, но вам все равно нужно это сделать: пометить язык. Помните, что C и C++ похожи на Haskell и Prolog. Без языкового тега мы не можем ответить, это спецификация языка, которая требует наблюдаемого поведения скомпилированной программы. –

+0

@MargaretBloom Спасибо за отзыв. Могу ли я пометить его двумя языками? –

+0

@ 彩 音 M: не следует одновременно отмечать C++ и java. Ответы могут быть очень разными. –

ответ

11

Условия гонки происходят, когда результат серии операций зависит от последовательности, на которой они выполняются.

В && и || операторы в C++ гарантии влево-вправо оценки и не оценить второй оператор, если первый ложно/верно, соответственно. Поскольку последовательность операций гарантирована, между операциями foo и операциями bar не существует условия гонки. Тем не менее, все еще могут быть условия гонки между операциями в каждом.

Код, нарушающий вышеупомянутые гарантии, не является кодом C++, а аналогичные компиляторы C++ никогда не будут генерировать код, который нарушает эти гарантии.

3

Дело, о котором идет речь, не является условием гонки. Давайте снова получить свой вопрос:

Будет ли когда-нибудь время, когда оценка короткого замыкания нарушается, и бар называется несмотря обув возвращает истину, или даже хуже, бар вызывается перед Foo называется , возможно, из-за многопоточности?

Я думаю, правильный вопрос будет:

Будет ли когда-нибудь время, когда бар называется ПОСЛЕFoo вызывается с ложной результата, но в то время как это называется foo вернет true, если будет называться ?

Хорошо, давайте сделаем код с потенциалом race condition.

#define BUFFER_SIZE 0x1000 
char globalBuffer[BUFFER_SIZE]; 

bool foo() { // have user an access to the path_to_file file? 
    return access(path_to_file, 0666) != 0; // path_to_file declared somewhere 
} 

bool bar() { 
    FILE *file = fopen(path_to_file, "r"); 
    if (file == NULL) return false; 
    char *ptr = gfets(globalBuffer, BUFFER_SIZE, file); 
    if (ptr == NULL) return false; 
    return true; 
} 
... 
result = foo() || bar(); // if foo is false, then user have an access 
printf("%s", globalBuffer); 

Предположим, что мы можем контролировать path_to_file.

состояние

гонки будет occuried, если мы делаем бесконечный цикл, как этот

#!/bin/bash 

while : 
do 
    ln -s /path/to/good/access/file /path/to/file 
    rm -f /path/to/file 
    ln -s /etc/shadow /path/to/file 
done 

После нескольких попыток, если ваше приложение с битом бит - я буду читать содержимое/и т.д./тень

Но вернемся к вашему вопросу:

Нет, никак. Если foo вернет true, bar не будет называться. Даже из других потоков, если у вас многопоточность. Каждый поток имеет свои собственные регистры, собственный стек. Таким образом, в случае двух вызовов функций в одном выражении вы должны следовать стандарту C++. Но это не значит, что код безопасен. И состояние гонки - одна из 100 возможных проблем.

+0

То, что вы демонстрируете, хорошо известно как ошибка TOCTOU - время-время-время использования. Это типично для древнего кода POSIX, который не мог полагаться на современные API-интерфейсы, которые выполняли атомистически в ядре и поэтому оставляли себя открытыми для гонок. –

+0

Есть много ошибок, которые могут быть использованы в комбинации. Я продемонстрировал сочетание состояния гонки с символическими ссылками вместе с ошибкой TOCTTOU. Некоторый другой пример может быть NULL разыменованием с условием гонки и даже UAF с условием гонки. – progopis

+0

Состояние гонки есть в многопоточном случае только. Мой пример эмулирует второй поток с скриптом оболочки, чтобы вызвать ошибку TOCTTOU. Цель моего ответа - показать, что если вы считаете, что код в безопасности, он может быть полностью небезопасным. – progopis

1

Обратите внимание, что C++ поддерживает перегрузку оператора, но это короткое замыкание - это функция только встроенных операторов (pre C++ 17), а не пользовательские перегрузки.

I.e. во всех текущих версиях C++ небезопасно предполагать, что короткое замыкание произойдет, если вы не знаете оба типа и знаете, что никакой оператор свободной функции || затеняет.

См. Для p0145, как это изменяется в C++ 17.

(полужирный является добавление, удаление Курсив)

Изменить пункт 5/2 следующим образом: [Примечание: Операторы могут быть перегружены, , которое, учитывая значение при применении к выражениям типа класса (Clause 9) или тип перечисления (7.2). Использование перегруженных операторов преобразуется в вызовы функций, как описано в 13.5. Перегруженные операторы подчиняются правила синтаксиса и оценки порядка, указанных в пункте 5 , но требования типа операнда *, * и категории значения, и оценку порядка заменяются правилами вызова функции. Отношения между операторами, такие как ++ a a + = 1, не являются , гарантированными для перегруженных операторов (13.5) и не гарантируются для операндов типа bool.-end note]

Смежные вопросы