• Jan Lindström's avatar
    MDEV-9422: Checksum errors on restart when killing busy instance that uses encrypted XtraDB tables · f448a800
    Jan Lindström authored
    Analysis:
    
    -- InnoDB has n (>0) redo-log files.
    -- In the first page of redo-log there is 2 checkpoint records on fixed location (checkpoint is not encrypted)
    -- On every checkpoint record there is up to 5 crypt_keys containing the keys used for encryption/decryption
    -- On crash recovery we read all checkpoints on every file
    -- Recovery starts by reading from the latest checkpoint forward
    -- Problem is that latest checkpoint might not always contain the key we need to decrypt all the
       redo-log blocks (see MDEV-9422 for one example)
    -- Furthermore, there is no way to identify is the log block corrupted or encrypted
    
    For example checkpoint can contain following keys :
    
    write chk: 4 [ chk key ]: [ 5 1 ] [ 4 1 ] [ 3 1 ] [ 2 1 ] [ 1 1 ]
    
    so over time we could have a checkpoint
    
    write chk: 13 [ chk key ]: [ 14 1 ] [ 13 1 ] [ 12 1 ] [ 11 1 ] [ 10 1 ]
    
    killall -9 mysqld causes crash recovery and on crash recovery we read as
    many checkpoints as there is log files, e.g.
    
    read [ chk key ]: [ 13 1 ] [ 12 1 ] [ 11 1 ] [ 10 1 ] [ 9 1 ]
    read [ chk key ]: [ 14 1 ] [ 13 1 ] [ 12 1 ] [ 11 1 ] [ 10 1 ] [ 9 1 ]
    
    This is problematic, as we could still scan log blocks e.g. from checkpoint 4 and we do
    not know anymore the correct key.
    
    CRYPT INFO: for checkpoint 14 search 4
    CRYPT INFO: for checkpoint 13 search 4
    CRYPT INFO: for checkpoint 12 search 4
    CRYPT INFO: for checkpoint 11 search 4
    CRYPT INFO: for checkpoint 10 search 4
    CRYPT INFO: for checkpoint 9 search 4 (NOTE: NOT FOUND)
    
    For every checkpoint, code generated a new encrypted key based on key
    from encryption plugin and random numbers. Only random numbers are
    stored on checkpoint.
    
    Fix: Generate only one key for every log file. If checkpoint contains only
    one key, use that key to encrypt/decrypt all log blocks. If checkpoint
    contains more than one key (this is case for databases created
    using MariaDB server version 10.1.0 - 10.1.12 if log encryption was
    used). If looked checkpoint_no is found from keys on checkpoint we use
    that key to decrypt the log block. For encryption we use always the
    first key. If the looked checkpoint_no is not found from keys on checkpoint
    we use the first key.
    
    Modified code also so that if log is not encrypted, we do not generate
    any empty keys. If we have a log block and no keys is found from
    checkpoint we assume that log block is unencrypted. Log corruption or
    missing keys is found by comparing log block checksums. If we have
    a keys but current log block checksum is correct we again assume
    log block to be unencrypted. This is because current implementation
    stores checksum only before encryption and new checksum after
    encryption but before disk write is not stored anywhere.
    f448a800
innodb-log-encrypt-crash.test 1.18 KB