>
>
>
V1076. Code contains invisible characte…


V1076. Code contains invisible characters that may alter its logic. Consider enabling the display of invisible characters in the code editor.

Анализатор обнаружил в тексте программы символы, которые могут ввести программиста в заблуждение. Эти символы могут не отображаться и изменять видимое представление кода в среде разработки. Комбинации таких символов могут привести к тому, что человек и компилятор будут интерпретировать код по-разному.

Это может быть сделано специально. Такой вид атаки называется Trojan Source. Подробнее:

Анализатор выдаст предупреждение, если найдет один из следующих символов:

Обозначение

Код

Название

Описание

LRE

U+202A

LEFT-TO-RIGHT EMBEDDING

Текст после символа LRE интерпретируется как вставленный и отображается слева направо. Действие LRE прерывается символом PDF или символом перевода строки.

RLE

U+202B

RIGHT-TO-LEFT EMBEDDING

Текст после символа RLE интерпретируется как вставленный и отображается справа налево. Действие RLE прерывается символом PDF или символом перевода строки.

LRO

U+202D

LEFT-TO-RIGHT OVERRIDE

Текст после символа LRO принудительно отображается слева направо. Действие LRO прерывается символом PDF или символом перевода строки.

RLO

U+202E

RIGHT-TO-LEFT OVERRIDE

Текст после символа RLO принудительно отображается справа налево. Действие RLO прерывается символом PDF или символом перевода строки.

PDF

U+202C

POP DIRECTIONAL FORMATTING

Символ PDF прерывает действие одного из символов LRE, RLE, LRO или RLO, встреченного ранее. Прерывает ровно один, последний из встреченных, символ.

LRI

U+2066

LEFT‑TO‑RIGHT ISOLATE

Текст после символа LRI отображается слева направо и интерпретируется как изолированный. Это означает, что другие управляющие символы не влияют на отображение этого фрагмента текста. Действие LRI прерывается символом PDI или символом перевода строки.

RLI

U+2067

RIGHT‑TO‑LEFT ISOLATE

Текст после символа RLI отображается справа налево и интерпретируется как изолированный. Это означает, что другие управляющие символы не влияют на отображение этого фрагмента текста. Действие RLI прерывается символом PDI или символом перевода строки.

FSI

U+2068

FIRST STRONG ISOLATE

Направление текста после символа FSI задается первым управляющим символом, не входящим в этот фрагмент текста. Другие управляющие символы не влияют на отображение этого текста. Действие FSI прерывается символом PDI или символом перевода строки.

PDI

U+2069

POP DIRECTIONAL ISOLATE

Символ PDI прерывает действие одного из символов LRI, RLI или FSI, встреченного ранее. Прерывает ровно один, последний из встреченных, символ.

LRM

U+200E

LEFT-TO-RIGHT MARK

Текст после символа LRM отображается слева направо. Действие LRM прерывается символом перевода строки.

RLM

U+200F

RIGHT-TO-LEFT MARK

Текст после символа RLM отображается справа налево. Действие RLM прерывается символом перевода строки.

ALM

U+061C

ARABIC LETTER MARK

Текст после символа ALM отображается справа налево. Действие ALM прерывается символом перевода строки.

ZWSP

U+200B

ZERO WIDTH SPACE

Неотображаемый пробельный символ. Использование символа ZWSP привести к тому, что разные строки будут отображаться одинаково. Например, 'str[ZWSP]ing' отображается как 'string'.

Рассмотрим следующий фрагмент кода:

#include <iostream>

int main()
{
  bool isAdmin = false;
  /*[RLO] } [LRI] if (isAdmin)[PDI] [LRI] begin admins only */ // (1)
      std::cout << "You are an admin.\n";
  /* end admins only [RLO]{ [LRI]*/                            // (2)
  return 0;
}

Изучим детально строку (1).

[LRI] if (isAdmin)[PDI]

Здесь символ [LRI] действует до символа [PDI]. Строка 'if (isAdmin)' будет отображаться слева направо и считается изолированной, получаем 'if (isAdmin)'.

[LRI] begin admins only */

Здесь символ [LRI] действует до конца строки. Получаем изолированную строку: 'begin admins only */'

[RLO] {пробел1}, '}', {пробел2}, 'if (isAdmin)', 'begin admins only */'

Здесь символ [RLO] действует до конца строки и отображает текст справа налево. Каждая из полученных в предыдущих пунктах изолированных строк рассматривается как отдельный неделимый символ. Получаем такую последовательность:

'begin admins only */', 'if (isAdmin)', {пробел2}, '{', {пробел1}

Обратите внимание, что символ закрывающей фигурной скобки теперь отображается как '{' вместо '}'.

Итоговый вид строки (1), который может быть отображен в редакторе:

/* begin admins only */ if (isAdmin) {

Похожие преобразования затронут и строку (2), которая отобразится так:

/* end admins only */ }

Финальный вид кода, который может отобразиться в редакторе:

#include <iostream>

int main()
{
  bool isAdmin = false;
  /* begin admins only */ if (isAdmin) { 
      std::cout << "You are an admin.\n";
  /* end admins only */ }
  return 0;
}

Ревьювер может посчитать, что в коде выполняется некоторая проверка перед выводом сообщения. Он проигнорирует комментарии и подумает, что код должен выполняться так:

#include <iostream>

int main()
{
  bool isAdmin = false;
  if (isAdmin) { 
    std::cout << "You are an admin.\n";
  }
  return 0;
}

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

#include <iostream>

int main()
{
  bool isAdmin = false;
  std::cout << "You are an admin.\n";
  return 0;
}

Теперь рассмотрим более простой и в то же время более опасный пример использования неотображаемых символов:

#include <string>
#include <string_view>

enum class BlockCipherType { DES, TripleDES, AES, /*....*/ };

constexpr BlockCipherType
StringToBlockCipherType(std::string_view str) noexcept
{
  if (str == "AES[ZWSP]")
    return BlockCipherType::AES;
  else if (str == "TripleDES[ZWSP]")
    return BlockCipherType::TripleDES;
  else
    return BlockCipherType::DES;
}

Функция 'StringToBlockCipherType' производит конвертацию строки в одно из значений перечисления 'BlockCipherType'. По коду можно сделать вывод, что функция возвращает три разных значения, однако это не так. Из-за того, что в конце каждого строкового литерала дописан неотображаемый пробельный символ [ZWSP], проверки на равенство со строками 'AES' и 'TriplesDES' будут ложными. В итоге из трех ожидаемых возвращаемых значений функция будет возвращать лишь 'BlockCipherType::DES'. В то же время код в редакторе может отображаться следующим образом:

#include <string>
#include <string_view>

enum class BlockCipherType { DES, TripleDES, AES, /*....*/ };

constexpr BlockCipherType
StringToBlockCipherType(std::string_view str) noexcept
{
  if (str == "AES")
    return BlockCipherType::AES;
  else if (str == "TripleDES")
    return BlockCipherType::TripleDES;
  else
    return BlockCipherType::DES;
}

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

Данная диагностика классифицируется как: