Crédito para @koraa.
Atualmente, este código exibe um uso após edição gratuita:
void bugged(void)
{
XXH3_state_t *a = XXH3_createState();
XXH3_state_t *b = XXH3_createState();
XXH3_64bits_reset_withSeed(a, 0x1234567890abcdefull);
XXH3_copyState(b, a);
XXH3_freeState(a);
// Bug
XXH3_64bits_update(b, "BUGGED", strlen("BUGGED"));
XXH3_freeState(b);
}
Isso se deve ao fato de a->secret
apontar para a->customSecret
, e XXH3_copyState
não atualizar esse ponteiro (ele é literalmente apenas memcpy
), então b->secret
pontos para a->customSecret
.
Após a
ser liberado, b->secret
é agora um ponteiro inválido.
Veja a discussão em # 361.
Existem algumas opções.
A primeira e mais simples opção que não altera o comportamento é esta:
XXH_PUBLIC_API XXH_errorcode
XXH3_copyState(XXH3_state_t* dst, const XXH3_state_t* src)
{
XXH_memcpy(dst, src, sizeof(XXH3_state_t));
if (src->secret == &src->customSecret[0]) {
dst->secret = &dst->customSecret[0];
}
return XXH_OK;
}
Isso é um pouco hackeado.
A segunda opção é usar um dos campos reservados para informar qual tipo de estado ele está e usá-lo em vez de comparar ponteiros brutos.
Enquanto fazemos isso, poderíamos adicionar verificação de erros para evitar a mistura de estados de 64 bits e 128 bits. Isso mudaria o comportamento, mas esse uso não foi intencional e pode ser considerado UB.
A terceira opção é usar um ponteiro NULL
para o modo propagado, e se for NULL, use customSecret
vez de secret
, mas isso adicionaria um branch. No entanto, já existe sobrecarga suficiente do gerenciamento de estado que provavelmente faria uma diferença imperceptível.
A última opção é malloc()
uma cópia do ponteiro secreto.
Isso mudará o ABI, pois não é mais possível executá-lo inteiramente na memória da pilha. Também será altamente redundante para kSecret
.
No entanto, isso permitirá que os segredos personalizados sejam invalidados após reset_withSeed()
e nos permitirá reduzir o tamanho do estado.
Opa, apertou acidentalmente o botão errado: facepalm:
Acho que há uma quarta opção: sempre copie o pool de entropia na estrutura; isso forçaria o pool a ter o tamanho do segredo padrão. Isso pode ser bom e pode formar a base para a otimização de geração de pool de entropia offline.
uint64_t XXH3_64bits_withEntropyPool(XXH3_state_t &pool, uint8_t data, size_t size);
A implementação iria "estender" o estado XXH3 com os dados fornecidos; retornar imediatamente o hash sem modificar o estado ...
EDITAR Isso preservaria a mobilidade; qual IMO é um grande benefício para a usabilidade e seria muito apreciado por uma integração com ferrugem.
uint64_t XXH3_64bits_withEntropyPool(XXH3_state_t &pool, uint8_t data, size_t size);
error: expected ')'
...XXH3_64bits_withEntropyPool(XXH3_state_t &pool, uint8_t data,...
^
note: to match this '('
uint64_t XXH3_64bits_withEntropyPool(XXH3_state_t &pool, uint8_t...
^
1 error generated.
O que é isso? Eu não sei o que você está tentando fazer aí ...: sorriso:
A mobilidade seria um bom recurso e seria fácil com um segredo de tamanho fixo.
Na verdade, tudo seria mais fácil (e mais rápido) com um segredo de tamanho fixo, mas não queremos bagunçar a API.
Acho que a melhor opção é o ponteiro NULL
.
Já existem 5 ramos no início de XXH3_update
. Não custa fazer mais um (especialmente porque só é necessário quando o buffer está cheio)
Também deve ser observado que x86_64 e i686 podem fazer isso sem ramificação:
lea tmp, [state + offsetof (state::customSecret)]
test secret, secret
cmove secret, tmp
Isso também não tem ramificações no ARM
cmp secret, #0
it eq
addeq secret, state, offsetof(state::customSecret)
e em AArch64:
add tmp, state, offsetof(state::customSecret)
cmp secret, #0
csel secret, tmp, secret, eq
Comentários muito úteis
Acho que a melhor opção é o ponteiro
NULL
.Já existem 5 ramos no início de
XXH3_update
. Não custa fazer mais um (especialmente porque só é necessário quando o buffer está cheio)