… И куда делась пуля?
[Из серии статей об ошибках компилятора шейдеров графического процессора.]
У нас есть неожиданная остановка в нашем туре: Apple. *
Я рассмотрю две проблемы с драйверами Apple PowerVR: одна вызывает рендеринг мусора, а вторая - исчезновение слага. Мы сообщили об этих проблемах в Apple. Параллельно с этим я делаю обзор различных метаморфических преобразований, которые использует GLFuzz.
Рендеринг мусора
Этот фрагментный шейдер WebGL из GLSLsandbox должен привести к рендерингу этого красивого изображения:

Попробуйте сами - посмотрите, поддерживает ли ваш браузер WebGL 1, перейдя сюда. Если это так, то вы сможете увидеть визуализируемый шейдер, посетив эту страницу [отказ от ответственности: это обычная страница WebGL, поэтому ее посещение должно быть безопасным, но вы делаете это на свой страх и риск!]:
GLFuzz создает вариантные шейдеры, которые принимают дополнительный параметр времени выполнения, injectionSwitch, для которого установлено значение (0.0, 1.0) во время выполнения. Как подробно обсуждалось в последнем посте, это означает, что условное выражение:
if(injectionSwitch.x > injectionSwitch.y) {
// Arbitrary syntactically valid code
}
не должно влиять на то, что вызывает рендеринг фрагментного шейдера - во время выполнения условие будет ложным.
Используя GLFuzz, мы обнаружили, что простая инъекция заставляет вышеуказанный шейдер отображать мусор на iPhone SE в Safari с WebGL.
Это вариант шейдера, и разница действительно простая - GLFuzz внедряет два экземпляра следующего фрагмента кода:
if(injectionSwitch.x > injectionSwitch.y) {
return;
}
Очевидно, это не должно влиять на то, что отрисовывается шейдером, но мы обнаруживаем, что отрисовывается мусор. Вот кадр из того, что мы видим:

А вот видео, на котором Пол снял проблему, проявляющуюся на его iPhone:
Проблема такого рода потенциально критична для безопасности: информация, скажем, из центра уведомлений может попасть в мусор. Затем мусор может быть захвачен веб-страницей через JavaScript и отправлен на сервер. Теоретически тогда можно будет собрать из мусора исходную информацию, которая просочилась внутрь.
Однако в этом случае мы считаем, что ошибка связана с очень низким риском, поскольку мусор не поддается расшифровке. Так что, если вам интересно, вы можете попробовать это на своем iPhone через эту страницу WebGL [обычный отказ от ответственности, который вы посещаете на свой страх и риск]:
Вы должны увидеть то же красивое изображение, связанное с исходным шейдером, но, возможно (если у вас iPhone с такими же драйверами, как у нас), вы тоже увидите мусор. Дайте нам знать!
Этот пример немного похож на ошибки неправильного изображения AMD, о которых мы сообщали в предыдущем посте: ошибка вызвана введением оператора недоступного перехода - в данном случае два возврата и комбинация операторов возврата и сброса в случае AMD. ошибки. Это согласуется с гипотезой Vu Le et al. в своей статье о тестировании EMI , что усложнение графа потока управления программой может быть эффективным для выявления крайних ошибок компилятора.
Если убрать либо инъекции, либо каким-либо образом упростить их, проблема исчезнет.
Убийство слизней
Наш следующий пример удобен, потому что он демонстрирует три преобразования GLFuzz одновременно - хорошее дополнение к обзору преобразований, который мы дадим в следующем разделе.
Этот шейдер, опять же из GLSLsandbox, отображает то, что, на наш взгляд, похоже на слаг:

Итак, в исходном коде шейдера говорится о «worm_color», но мы считаем, что это слаг (а также мы считаем, что цвет - это правильное написание цвета! :-)).
Но, червь или слизень, GLFuzz приступает к убийству бедняги следующим образом.
Сначала GLFuzz берет этот фрагмент кода:
if(j == 0) {
return vec4(0.8, 0.5, 0.5, 1.0);
}
и заменяет его на:
for(int c = 0; c < 1; c++) {
if(j == 0) {
return vec4(0.8, 0.5, 0.5, 1.0);
}
}
Очевидно, это не должно иметь никакого влияния, не так ли? Цикл «c» выполняет только одну итерацию, поэтому функционально его может и не быть.
Затем GLFuzz принимает этот оператор возврата:
return vec4(0.8, 0.7, 0.4, 1.0);
и умножает первый компонент вектора на 1,0:
return vec4(0.8 * injectionSwitch.y, 0.7, 0.4, 1.0);
Теперь инъекцияSwitch.y может не выглядеть как 1.0, но во время выполнения это значение, которое он будет хранить.
Наконец, прямо в начале main GLFuzz вставляет пустое условие с ложной защитой:
if(injectionSwitch.x > injectionSwitch.y) { }
Защита оказывается ложной (во время выполнения) из-за способа, которым мы инициализируем инъекциюSwitch, но здесь это даже не имеет значения, поскольку тело условного выражения пусто.
Вот вариант шейдера, в котором применены все три преобразования. В результате бедный слизняк погибает:

Опять же, вы можете попробовать это на себе:
- Вот веб-страница, которая позволит вам выполнять рендеринг с использованием исходного шейдера
- Вот один с вариантом шейдера
Если у вас iPhone, было бы интересно узнать, сможете ли вы воспроизвести проблему. Если бы вы могли воспроизвести проблему на другом устройстве, это было бы еще интереснее!
Отмена любого из этих преобразований устраняет проблему.
Изучение преобразований GLFuzz
Если вы хотите узнать немного больше о том, как эти преобразования работают в целом, прочтите, пожалуйста, первую в параллельной серии публикаций о том, как работает GLFuzz.
* Как обсуждалось в моем первом посте, план состоял в том, чтобы в алфавитном порядке совершить поездку по шести основным разработчикам графических процессоров, протестировать компоненты компилятора шейдеров в их драйверах. В рамках этого мы стремились протестировать драйверы для графических процессоров серии PowerVR от Imagination Technologies, которые встроены в большинство iPhone.
Мы нашли и сообщили о некоторых интригующих проблемах. В процессе мы узнали, что Apple сами на самом деле пишут драйверы PowerVR для iPhone, так что на самом деле мы тестируем компиляторы шейдеров Apple, а не Imagination.
По совпадению, Apple следует за AMD в алфавитном порядке, поэтому мы остановим Apple сейчас и отложим ARM до следующей остановки. Надеюсь, мы рассмотрим Imagination позже, если сможем достать устройства, использующие их драйверы.