Hoje de manhã eu recebi uma pergunta de um amigo que queria saber como corromper um banco de dados, para fazer alguns testes. O mecanismo que ele estava utilizando não era efetivo, então eu mencionei que ele poderia usar um editor hexadecimal para fazer o trabalho, mas como não temos muitas referências em português, resolvi gastar alguns minutinhos para machucar um banco de dados. Vamos?


Criando o banco de dados

Execute o script abaixo para criarmos um coitado que será corrompido…

USE master
go

-- drop database BDProblema

CREATE DATABASE BDProblema
go

USE BDProblema
go

ALTER DATABASE BDProblema
SET PAGE_VERIFY NONE
go

CREATE TABLE Dados (
IdDado INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
Nome VARCHAR(200) NOT NULL)
go

INSERT INTO Dados VALUES ('Luciano Caixeta Moreira')
go


Note que eu configurei o banco de dados para não trabalhar com nenhum modelo de verificação de página, pois quero mostrar para vocês uma edição do arquivo sem recebermos nenhuma mensagem de erro.


Analisando a tabela criada com DBCC PAGE

Agora vamos dar uma olhada na estrutura da página. Primeiro descobrimos onde ela está e depois usando o DBCC PAGE para vermos sua estrutura.

SELECT *
FROM sys.sysindexes as s
where s.id = OBJECT_ID('Dados')
/*
    First: 0x900000000100
    Arquivo = 1
    Página = 144 (0x90)
*/

DBCC TRACEON(3604)
DBCC PAGE (BDProblema, 1, 144, 2)

/*
Vamos guardar o início da página, pois é o que importa!

Memory Dump @0x5C19C000

5C19C000:   01010400 00c00001 00000000 00000800 †.....À..........        
5C19C010:   00000000 00000100 1b000000 781f8600 †............x.†.        
5C19C020:   90000000 01000000 14000000 40000000 †............@...        
5C19C030:   13000000 00000000 00000000 00000000 †................        
5C19C040:   01000000 00000000 00000000 00000000 †................        
5C19C050:   00000000 00000000 00000000 00000000 †................        
5C19C060:   30000800 01000000 02000001 0026004c †0............&.L        
5C19C070:   75636961 6e6f2043 61697865 7461204d †uciano Caixeta M        
5C19C080:   6f726569 72610000 21212121 21212121 †oreira..!!!!!!!!        

*/

Agora vamos executar uma pequena alteração para identificarmos se o 0x4c em negrito é realmente o “L” do meu nome.

UPDATE Dados
SET Nome = 'Muciano Caixeta Moreira'

DBCC TRACEON(3604)
DBCC PAGE (BDProblema, 1, 144, 2)

/*

5C19C000:   01010400 00c00001 00000000 00000800 †.....À..........        
5C19C010:   00000000 00000100 1b000000 781f8600 †............x.†.        
5C19C020:   90000000 01000000 14000000 5b000000 †............[...        
5C19C030:   02000000 00000000 00000000 00000000 †................        
5C19C040:   01000000 00000000 00000000 00000000 †................        
5C19C050:   00000000 00000000 00000000 00000000 †................        
5C19C060:   30000800 01000000 02000001 0026004d †0............&.M        
5C19C070:   75636961 6e6f2043 61697865 7461204d †uciano Caixeta M        
5C19C080:   6f726569 72610000 21212121 21212121 †oreira..!!!!!!!!

*/

Aqui vemos que o caractere na posição 0x5C19C06F alterou de 0x4c (L) para 0x4d (M).
Como estamos na página 144, sabemos que o offset dessa página no arquivo em bytes é: 144 * 8192 = 1179648 (em hexadecimal –> 0x120000). Vamos para o editor hexadecimal?

Editando o arquivo BDProblema.mdf

Pare a instância do SQL Server em questão e utilizando o editor hexadecimal de sua preferência (baixei o Free Hex Editor Neo da HDD Software) abra o arquivo BDProblema.mdf.

Navegue até o offset 120000 e a partir daí vá até a posição 6F e encontre o caractere 4d, alterando-o para 4c.

(Antes)
image

(Depois)
image


Agora salve o arquivo, feche o editor, reinicie a instância do SQL Server e execute um SELECT na tabela Dados. Qual será o resultado??? O “Muciano” voltou a ser “Luciano”. Até aqui não vemos nenhum erro, pois o SQL Server não estava com nenhuma verificação específica para o banco de dados, o que está para mudar…


Habilitando o page checksum e corrompendo o banco

Primeiramente, execute o script abaixo:

ALTER DATABASE BDProblema
SET PAGE_VERIFY CHECKSUM
go

UPDATE Dados
SET Nome = 'Muciano Caixeta Moreira'
go

DBCC CHECKDB()
GO

DBCC results for 'Dados'.
There are 1 rows in 1 pages for object "Dados".
CHECKDB found 0 allocation errors and 0 consistency errors in database 'BDProblema'.


Como resultado, teremos um banco de dados com CHECKSUM habilitado e a página com seu checksum calculado, pois foi executado um update na tabela. O CHECKDB vai executar com sucesso e nenhum erro será reportado.

Repita do procedimento listado no tópico “Editando o arquivo BDProblema.mdf”, onde você vai alterar o nome “Muciano” para “Luciano” através do editor hexadecimal. Reinicie o serviço do SQL Server e tente executar a consulta abaixo:

SELECT *
FROM Dados
/*
IdDado      Nome
----------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Msg 824, Level 24, State 2, Line 1
SQL Server detected a logical consistency-based I/O error: incorrect checksum (expected: 0xd73d977b; actual: 0xd73d97fb). It occurred during a read of page (1:144) in database ID 7 at offset 0x00000000120000 in file 'C:\Program Files\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\DATA\BDProblema.mdf'.  Additional messages in the SQL Server error log or system event log may provide more detail. This is a severe error condition that threatens database integrity and must be corrected immediately. Complete a full database consistency check (DBCC CHECKDB). This error can be caused by many factors; for more information, see SQL Server Books Online.
*/

Se tentar executar um CHECKDB, vai ver algo parecido com:

DBCC results for 'Dados'.
Msg 8928, Level 16, State 1, Line 1
Object ID 2105058535, index ID 1, partition ID 72057594038779904, alloc unit ID 72057594039697408 (type In-row data): Page (1:144) could not be processed.  See other errors for details.
Msg 8939, Level 16, State 98, Line 1
Table error: Object ID 2105058535, index ID 1, partition ID 72057594038779904, alloc unit ID 72057594039697408 (type In-row data), page (1:144). Test (IS_OFF (BUF_IOERR, pBUF->bstat)) failed. Values are 12716041 and -4.
There are 0 rows in 0 pages for object "Dados".
CHECKDB found 0 allocation errors and 2 consistency errors in table 'Dados' (object ID 2105058535).
CHECKDB found 0 allocation errors and 2 consistency errors in database 'BDProblema'.
repair_allow_data_loss is the minimum repair level for the errors found by DBCC CHECKDB (BDProblema).
DBCC execution completed. If DBCC printed error messages, contact your system administrator.


Pronto! Você está com o seu banco de dados corrompido.
Como é um problema na página de dados, não podemos fazer um rebuild de um índice (método utilizado quando um índice não-cluster está corrompido) e temos que partir para outros métodos de disaster recovery, como recuperar um backup.

Espero que você tenha gostado e te ajudado a entender como pode simular falhas no seu banco de dados, para testar seu planejamento de disaster recovery, antes que seu ambiente de produção não esteja ok e você não sabia o que fazer. Além disso, claro, veja como é importante manter o checksum habilitado!

Até a próxima!

[]s
Luciano Caixeta Moreira
luciano.moreira@microsoft.com
===============================================
This post is provided "AS IS" and confers no right
===============================================