Strategia per la correzione di avvisi firmati / non firmati

5

Sto lavorando per ottenere una compilazione pulita con -Wsign-conversion per il codice della libreria esistente. La biblioteca ha 25 anni o giù di lì. Alla fine, questa barra più alta sarà un varco di sicurezza e tutti i check-in dovranno essere soddisfatti o rifiutati.

Il codice della libreria esistente fa una discreta quantità di quanto segue (una semplificazione grossolana che cattura l'essenza):

class SHA1 : <inherited interfaces (contracts) and classes (behaviors)>
{
    ...
    enum {DIGEST_SIZE = 20U};
    unsigned int MaxDigestSize() { return DIGEST_SIZE; }
    ...
    int m_digestSize;
}

Quindi, la libreria fa cose come (l'interazione è più complessa, ma questa cattura l'essenza):

if(m_digestSize == -1)
    m_digestSize = MaxDiegstSize();

I chiamanti esterni possono usarlo come:

SHA1 sha;
...

// Calculate truncated digest
sha.Final(buffer, 8 /*signed or unsigned*/);
// Calculate using full digest
sha.Final(buffer, -1 /*signed*/);
// Calculate using full digest
sha.Final(buffer, sha.MaxDigestSize() /*unsigned*/);

Il motivo per cui -1 è popolare è il seguente "funziona" (si noti il passaggio da SHA-1 a SHA-512 ):

SHA512 sha;

// Calculate using full digest
sha.Final(buffer, -1 /*signed*/);

Per la libreria, vogliamo convertire in unsigned types per schiacciare l'avviso. Quindi, imposta un enum {DEFAULT_DIGEST_VALUE=...}; per gestire il caso di "default digest" che era compatibile con il "modello -1" esistente. Infine, rimuovi i confronti in -1 e utilizza un confronto con DEFAULT_DIGEST_VALUE .

Ma enum {DEFAULT_DIGEST_VALUE=std::numeric_limits<unsigned int>::max()}; si è rivelato un vicolo cieco a causa di limitazioni in numeric_limits .

Qualcuno ha suggerito di usare UINT_MAX , ma non sono sicuro se dovrei usarlo (OS X non ha <cstdint> , quindi introduce una dipendenza dell'intestazione C). Altri potenziali candidati sono __INT_MAX__ * 2U + 1 (da <limits> su OS X) e ~(0) (popolare in molti codici libreria, ma forse non corretto). In effetti, non sono sicuro che questo sia il percorso corretto in generale.

Qual è il modo corretto o migliore per:

  • passa a unsigned per coerenza ed eventuale gate di sicurezza
  • gestisci il "modello -1" per i chiamanti esistenti, non librerie
  • fornire una soluzione portatile, in C ++
  • risolve i potenziali problemi con i pattern di bit

Le mie scuse per aver posto una semplice domanda. Sono un architetto della sicurezza per mestiere. Ho bisogno dell'aiuto degli architetti del software perché non ho esperienza di ingegneria del software.

Dai commenti, ecco alcune ulteriori informazioni sui vincoli:

Codice esistente : la libreria esiste già e si chiama Crypto ++ . È stato creato negli anni '90 e gode di uno sviluppo continuo. È multipiattaforma e funziona su BSD, Linux, OS X, Solaris, Windows. La soluzione deve supportare standard più vecchi come C ++ 98 e C ++ 03. Possiamo abbandonare il supporto per C ++ 98 se la comunità è d'accordo.

Modifiche ABI - possiamo modificare l'API se la comunità è d'accordo. Sono abbastanza certo che ciò accadrà quando modificheremo la politica di una "compilazione pulita come porta di sicurezza". Non è necessario avere una compatibilità al 100%, ma dobbiamo gestire un codice base esistente di utenti che utilizzano -1 nel campo. Possiamo anche dare agli utenti l'avviso di modificare il loro codice con la macro del preprocessore CRYPTOPP_MAINTAIN_BACKWARDS_COMPATIBILITY . Ma alla fine, le cose devono "funzionare" per loro.

    
posta jww 08.07.2015 - 05:21
fonte

3 risposte

5

Bene dal mio punto di vista, stai cercando di risolvere il problema sbagliato. Si utilizza il codice di errore "-1" per indicare che è necessario utilizzare MaxDigestSize . Questa è una tecnica che Robert C. Martin chiama "Mental mapping" nel suo libro Pulisci codice . Il tuo conflitto signed / unsigned è causato esclusivamente dal fatto che stai usando questo "codice di errore". Quindi il problema che dovresti risolvere è "Come evitare di usare un codice di errore".

Ci si arriva:

L'approccio più semplice: crea m_digestSize e il parametro per sha.Final() entrambi unsigned ints . Ciò garantisce che il metodo Final possa accettare solo valori validi. Ora invece di dare al tuo metodo un codice di errore, basta sovraccaricare con un parametro predefinito come:

Final(byte* buffer, unsigned int digest = DEFAULT_DIGEST);

Nell'esempio che hai fornito questo DEFAULT_DIGEST è basato sul valore immutabile di MaxDigestSize() e sarebbe quindi 20.

EDIT:

In base alle nuove informazioni contenute nei commenti e nelle modifiche: Mi sembra che i grandi problemi siano gli avvertimenti. Soluzione migliore: verifica la presenza di -1 prima che assegna il parametro alla variabile membro. Inoltre, come menzionato nei commenti, dovresti avvolgere la primitiva in una piccola struttura e offrirla come alternativa mentre contrassegni la vecchia funzione come deprecata:

Final(byte* buffer, int digest = DEFAULT_DIGEST) // Mark as deprecated
{
    if(digest < MINIMUM_DIGEST)
    {
        digest = DEFAULT_DIGEST;
    }
    m_digestSize = static_cast<unsigned int>(digest);
    [...]
}

Final(byte* buffer, Digest digest = Digest())
{
    m_digestSize = digest;
    [...]
}

class Digest
{
    public:
    Digest(usigned int value = DEFAULT_DIGEST)
    :digestValue(value){};

    unsigned int GetValue()
    {
        return digestValue
    };

    private:
    unsigned int digestValue;
}
    
risposta data 08.07.2015 - 09:11
fonte
3

Questo è un caso in cui il design di un'API è più C idioso (e nemmeno buono C idiomatico) piuttosto che C ++ idiomatico.

Se i chiamanti inseriscono esplicitamente -1 o il risultato di MaxDigestSize() , significa che sanno in anticipo che stanno utilizzando quel valore predefinito. Significa anche che possono sfruttare il concetto C ++ di un valore predefinito per un argomento:

class SHA {
public:
  static const size_t MAX_DIGEST_SIZE = 20;
   ...
  Final(Buffer buffer, size_t digest_size = MAX_DIGEST_SIZE);
};

I chiamanti che desiderano il digest più grande invocano Final(buffer) e quelli che vogliono quelli più brevi invocano Final(buffer, digest_size) . La cosa importante qui è che questo mantiene tutta la comprensione del comportamento predefinito all'interno della classe ed elimina i numeri magici e il portello di fuga firmato usato per supportarli.

Ovviamente, dovrai passare tutte le invocazioni di Final() e cambiare quelle usando -1 o MaxDigestSize() per eliminare i loro secondi argomenti, ma questa è una buona cosa perché fa uscire il problema ai bambini della base di codice. È noioso, ma ne vale la pena.

Se sei costretto a non essere in grado di rimuovere queste cose dal codice esistente, non sarai in grado di passare ai valori senza segno e prevedere una compilazione senza avvisi. Il modo migliore per procedere in questo caso sarebbe avere Final() convalidare il suo input e generare un'eccezione se è fuori dai limiti previsti. (Il codice dovrebbe farlo comunque per sicurezza.)

    
risposta data 08.07.2015 - 16:37
fonte
2

Sembra che tu abbia il codice C, quindi dimentica c ++ se lo fai e resta fedele a quello che è.

Convertire tutto in unsigned è una buona idea, far sì che i tuoi tipi siano giusti è una buona cosa. Vorrei cogliere l'occasione per rimuovere int e sostituire con un tipo di dimensione noto, ad esempio long o __int32 in modo da sapere quanti byte di dati vengono manipolati (int è di solito a 32 bit, ma a volte isn ' t). Direi che era importante per il software di sicurezza.

Quindi, se la tua int è a 32 bit, puoi impostare max_digest su 0xffffffff (che è ciò che sta facendo ~ 0, invertendo ogni bit, quindi tutti gli 0 diventano 1s - di 0x0 diventa 0xffffffff, senza avere per sapere quanti bit sono coinvolti)

    
risposta data 08.07.2015 - 10:23
fonte

Leggi altre domande sui tag