Em caso de inteiro transborda o que é o resultado de (unsigned int) * (int)? sem sinal ou int?
Pergunta
Em caso de inteiro transborda o que é o resultado de (unsigned int) * (int)
? unsigned
ou int
? Que tipo faz o operador de índice de matriz (operator[]
) tomar para char*
:? int
, unsigned int
ou qualquer outra coisa
Eu estava auditar a seguinte função, e de repente esta questão surgiu. A função tem uma vulnerabilidade na linha 17.
// Create a character array and initialize it with init[]
// repeatedly. The size of this character array is specified by
// w*h.
char *function4(unsigned int w, unsigned int h, char *init)
{
char *buf;
int i;
if (w*h > 4096)
return (NULL);
buf = (char *)malloc(4096+1);
if (!buf)
return (NULL);
for (i=0; i<h; i++)
memcpy(&buf[i*w], init, w); // line 17
buf[4096] = '\0';
return buf;
}
Considere tanto w
e h
são muito grandes inteiros sem sinal. A multiplicação na linha 9 tem a chance de passar a validação.
Agora o problema é na linha 17. int i
Multiply com unsigned int w
: se o resultado for int
, é possível que o produto é negativo, resultando no acesso a uma posição que é antes buf
. Se o resultado for unsigned int
, o produto será sempre positivo, resultando no acesso a uma posição que é depois buf
.
É difícil escrever código para justificar esta: int
é muito grande. Alguém tem ideias sobre isso?
Existe alguma documentação que especifica o tipo de produto? Eu tenho procurado, mas até agora não encontraram nada.
Suponho que, tanto quanto a vulnerabilidade está em causa, se (unsigned int) * (int)
produz unsigned int
ou int
não importa, porque no arquivo objeto compilado, eles são apenas bytes. O código a seguir funciona o mesmo, não importa o tipo do produto:
unsigned int x = 10;
int y = -10;
printf("%d\n", x * y); // print x * y in signed integer
printf("%u\n", x * y); // print x * y in unsigned integer
Portanto, não importa qual o tipo a multiplicação retornos. É importante que se a função consumidor toma int
ou unsigned
.
A questão aqui é não o quão ruim a função é, ou como melhorar a função para torná-lo melhor. A função sem dúvida, tem uma vulnerabilidade. A pergunta é sobre o comportamento exato da função, com base no comportamento prescrito dos padrões.
Solução
Para responder à sua pergunta:. O tipo de uma expressão multiplicando um int e um int não assinado será um int não assinado em C / C ++
Para responder à sua pergunta implícita, de uma forma decente para lidar com possível estouro na aritmética inteira é usar o conjunto "IntSafe
" de rotinas da Microsoft:
http://blogs.msdn.com/michael_howard /archive/2006/02/02/523392.aspx
Está disponível no SDK e contém implementações em linha para que você possa estudar o que eles estão fazendo, se você estiver em outra plataforma.
Outras dicas
fazer o w h cálculo * no longa, longa, verifique se maior do que MAX_UINT
EDIT: alternativa: se sobrevoados (w * h) / h = w (?!? É este sempre é o caso deve ser, à direita)
Certifique-se que w * h não transborde, limitando w e h.
O tipo de w*i
não está assinado no seu caso. Se eu ler o padrão corretamente, a regra é que os operandos são convertidos para o tipo maior (com a sua signedness), ou sem assinatura tipo correspondente ao tipo assinado (que é unsigned int
no seu caso).
No entanto, mesmo que seja assinado, ele não impede a envolvente (escrita à memória antes buf
), porque ele poderia ser o caso (em i386 plataforma, que é), que p[-1]
é o mesmo que p[-1u]
. De qualquer forma, no seu caso, tanto buf[-1]
e buf[big unsigned number]
seria um comportamento indefinido, de modo que o assinaram pergunta / sem assinatura não é tão importante.
Note que assinou / matérias não assinados em outros contextos - por exemplo. (int)(x*y/2)
dá resultados diferentes dependendo dos tipos de x
e y
, mesmo na ausência de um comportamento indefinido.
Gostaria de resolver o seu problema através da verificação de estouro na linha 9; desde 4096 é uma muito pequena constante e 4096 * 4096 não transborde na maioria das arquiteturas (você precisa verificar), eu faria
if (w>4096 || h>4096 || w*h > 4096)
return (NULL);
Esta deixa de fora o caso quando w
ou h
são 0, você pode querer verificar para ele, se necessário.
Em geral, você pode verificar para estouro assim:
if(w*h > 4096 || (w*h)/w!=h || (w*h)%w!=0)
Em C / C ++ a notação p[n]
é realmente um atalho para a escrita *(p+n)
, e este aritmética de ponteiro leva em consideração o sinal. Então p[-1]
é válido e se refere ao valor imediatamente antes *p
.
Assim, o sinal realmente importa aqui, o resultado do operador de aritmética com inteiros seguir um conjunto de regras definidas pela norma, e isso é chamado inteiro promoções.
Visitar esta página: INT02-C. Entenda inteiro regras de conversão
2 alterações torná-lo mais seguro:
if (w >= 4096 || h >= 4096 || w*h > 4096) return NULL;
...
unsigned i;
Note também que não menos é uma má idéia para escrever ou ler além do fim do buffer. Portanto, a questão não é se i w pode tornar-se negativo, mas se 0 <= i h + w <= 4096 mantém-se.
Portanto, não é o tipo que importa, mas o resultado de h * i. Por exemplo, não faz diferença se isso é (não assinada) 0x80000000 ou (int) 0x80000000, o programa irá seg-fault qualquer maneira.
Para C, consulte "conversões usuais aritméticas". (C99: Secção 6.3.1.8, ANSI C K & R A6.5) para detalhes sobre como os operandos dos operadores matemáticos são tratados
No seu exemplo as seguintes regras aplicam-se:
C99:
Caso contrário, se o tipo do operando com assinado tipo inteiro pode representar todos os valores do tipo do operando com tipo inteiro sem sinal, em seguida, o operando com inteiro sem sinal tipo é convertido para o tipo de operando com inteiro assinado tipo.
Caso contrário, ambos os operandos são convertidos para o tipo inteiro sem sinal correspondente ao tipo da operando com inteiro assinado tipo.
ANSI C:
Caso contrário, se qualquer operando for unsigned int, o outro é convertido para int não assinado.
Porque não basta declarar i como int não assinado? Então, o problema desaparece.
Em qualquer caso, i * w é garantido para ser <= 4096, como os testes de código para isso, por isso nunca vai transbordar.
memcpy (& buf [i w> -1 i w <4097 i W: 0: 0], o init, w); Eu não acho que o cálculo triplo da i w faz degradar a perfomance)
w * h pode transbordar se w e / ou h são suficientemente grande e o seguinte validação poderia passar.
9. if (w*h > 4096)
10. return (NULL);
No int, operações mistas unsigned int, int é elevado a int não assinado, caso em que, um valor negativo de 'i' se tornaria um grande valor positivo. Nesse caso
&buf[i*w]
seria acesso a um fora de valor vinculado.
aritmética sem sinal é feito como modular (ou wrap-around), de modo que o produto de dois grandes ints não assinados podem facilmente ser inferior a 4096. A multiplicação de int e int não assinado resultará em um int não assinado (ver secção 4.5 do C ++ padrão).
Por isso, dado grande w e um valor adequado de h, você pode realmente entrar em apuros.
Certificar-se de aritmética inteira não estouro é difícil. Uma maneira fácil é converter a ponto flutuante e fazendo uma multiplicação de ponto flutuante, e ver se o resultado é de todo razoável. Como QWERTY sugeriu, longa, longa seria útil, se disponível no seu implementação. (É uma extensão comum em C90 e C ++, existe em C99, e estará em C ++ 0x).
Existem 3 parágrafos no actual projecto de C1X no cálculo (UNSIGNED TIPO1) X (TIPO2 ASSINADO) em 6.3.1.8 coversions aritméticas usuais, N1494,
WG 14: C - A situação do projeto e marcos
Caso contrário, se o operando que tem tipo inteiro não assinado tem posto maior ou igual à classificação do tipo do outro operando, então o operando com Tipo inteiro assinado é convertido para o tipo de operando com unsigned tipo inteiro.
Caso contrário, se o tipo do operando com o tipo inteiro assinado pode representar todos os valores do tipo do operando com o tipo inteiro sem sinal, então operando com tipo inteiro sem sinal é convertido para o tipo de operando com inteiro assinado tipo.
De outro modo, ambos os operandos são convertidos para o tipo inteiro sem sinal correspondente ao tipo do operando com o tipo inteiro assinado.
Portanto, se um não está assinado int e b é int, análise de (a * b) deve gerar o código (a * (unsigned int) b). Transbordará se b <0 ou a * b> UINT_MAX.
Se um não está assinado int e b é longa de tamanho maior, (a * b) deve gerar ((long) a * (longo) b). Transbordará se a * b> LONG_MAX ou a * b Se um não está assinado int e b é longo do mesmo tamanho, (a * b) deve gerar ((unsigned long) a * (unsigned long) b). Transbordará se b <0 ou a * b> ULONG_MAX. Em sua segunda pergunta sobre o tipo esperado pelo "indexador", a resposta aparece "tipo inteiro", que permite que qualquer (assinado) índice inteiro. 6.5.2.1 Matriz subscripting Restrições 1 Uma das expressões terão tipo ‘‘ponteiro para tipo de objeto completo’’, o outro
expressão deve ter tipo integer, eo resultado tem tipo ‘‘tipo’’. Semântica 2 A expressão sufixo seguido por uma expressão entre parêntesis rectos [] é um subscripted
designação de um elemento de um objecto matriz. A definição do operador subscrito []
é que E1 [E2] é idêntico a (* ((E1) + (E2))). Por causa das regras de conversão que
aplicar ao operador + binário, se E1 é um objeto de matriz (de modo equivalente, um ponteiro para o
elemento inicial de um objecto matriz) e E2 é um número inteiro, E1 [E2] designa a E2-th
elemento de E1 (contagem de zero). Cabe para o compilador para executar uma análise estática e avisar o desenvolvedor sobre possibilidade de transbordo na memória intermédia, quando a expressão de ponteiro é uma variável de matriz e o índice pode ser negativo. O mesmo vale cerca de advertência sobre derrapagens tamanho possível da matriz, mesmo quando o índice é positivo ou não assinado.
Para realmente responder sua pergunta, sem especificar o hardware que está sendo executado, você não sabe, e em código pretende ser portátil, você não deve depender de qualquer comportamento particular.