暂无图片
暂无图片
5
暂无图片
暂无图片
暂无图片

偷梁换柱:国产数据库如何防范误写入?

数据和云 2024-08-12
958
前几天和用户交流,提到了数据库中的一种情形,那就是,数据库如何防范数据库外部的改写或篡改。也就是说,如果操作系统管理员(SA),不经过数据库的SQL/DML操作,改写了数据,数据库有办法检测到吗?

我们先说结论:

1. 外部数据篡改、侵入的情况确实可能存在(后面我举例说明),通过OS级别的数据块替换,可以改写数据(更不要说类似BBED的工具);

2. 大部分自研的国产数据库,还做不到精确校验和防范这类情况,我测试过几种,大家可以针对性的测试一下。由开源演进而来的国产数据库,具备一定的写丢失安全检测能力。

3. Oracle在 12.2 版本开始实现了严谨的写丢失检测机制。

今天的数据库体系已经非常庞大,所以斯通布雷克说的:站在前人的肩膀上,而不是站在他们的脚趾上。这句话很有一些道理。在开源基础上演进,就能够集成其持续积累,而从头写作一个数据库,所有的工作都需要重来一次,任重而道尤远。
【外部写损坏案例】


关于数据库的文件损坏,我曾经遇到这样一个案例。在帮助用户恢复数据库时,发现了罕见的归档日志损坏。如下操作过程,在进行归档recover时,数据库报错,提示归档日志损坏: 

***
Corrupt block seq: 37288 blocknum=1.
Bad header found during deleting archived log
Data in bad block - seq:810559520. bno:170473264. time:707406346
beg:21280 cks:21061
calculated check value: 9226
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
***

信息比较详细,说37288号归档日志Header损坏,无法读取数据。提一个小问题:如果你遇到了这样的错误?会怎样思考和处置?
现在需要分析:为什么日志会损坏?是如何损坏的?
我首先要做的就是,看看日志文件的内容,通过最简单的命令将日志文件中的内容输出出来:
    strings arch_1_37288_632509987.dbf > log.txt
    然后检查生成的这个日志文件,我就发现了问题。在这个归档日志文件中,被写入了大量的跟踪文件内容,其中开头部分就是一个跟踪文件的全部信息。
    这是一种我从来没有遇到过的现象,也就是说,当数据库调用操作系统I/O子命令在写出跟踪文件时,错误的覆盖掉了已经存在的归档文件,最后导致归档日志损坏,非常奇妙,从所未见。
    这个故障应当是操作系统在写出时出现了问题,存在文件的空间仍然被认为是可写的,这样就导致了写冲突,出现这类问题,应当立即检查硬件,看看是否是硬件问题导致了如此严重的异常。这就是静默错误的一种情况,对现有文件写入了错误的数据,并且没有发出任何错误预警:

    Dump file ADMIN/bdump/erp_p007_19216.trc
    Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Production
    With the Partitioning, OLAP and Data Mining options
    ORACLE_HOME = DBMS/erp/erpdb/10g
    Linux
    2.6.9-34.ELhugemem
    i686
    Instance name: erp
    Redo thread mounted by this instance: 1
    Oracle process number: 22
    Unix process pid: 19216, image: oracle@eygle.com (P007)
    KCRP: blocks claimed = 61, eliminated = 0
    ----- Recovery Hash Table Statistics ---------
    Hash table buckets = 32768
    Longest hash chain = 1
    Average hash chain = 61/61 = 1.0
    Max compares per lookup = 0
    Avg compares per lookup = 0/61 = 0.0
    ----------------------------------------------
    ----- Recovery Hash Table Statistics ---------
    Hash table buckets = 32768
    Longest hash chain = 1
    Average hash chain = 61/61 = 1.0
    Max compares per lookup = 1
    Avg compares per lookup = 1426/1426 = 1.0
    ----------------------------------------------
    \GPAYMENTdxn
    AP_CHECKS
    PaymentsN
    a'VND
    Userxn
    AP_INVOICE_PAYMENTS
    105273
    5406105305-20101020-003
    3001CASH CLEARING
    CREATED

    类似的故障肯定并不少见,很多时候可能我们并未追根究底,这更说明进行多角度的数据损坏防范就变得非常重要针对数据文件,在 Oracle 12c中,通过 ASM 实现的 ASMFD特性,Oracle 可以将外部写错误完全隔绝。这是一种更全面的保护
    【写丢失保护特性】


    Oracle 11g中,引入了DB_LOST_WRITE_PROTECT特性,通过这个参数可以启用或禁用丢失的写入检测。当I/O子系统确认块写入完成,而实际上写入没有或错误的发生在持久存储中,就意味着发生了数据块写入丢失,这个特性用于检测这种情况。

    参数 DB_LOST_WRITE_PROTECT 有三个选项设置,分别是NONETYPICALFULL

    l当主数据库上的参数设置为TYPICAL时,实例会在重做日志中记录读写(Read Write)表空间的Buffer Cache读取信息,这对于检测丢失的写入是必需的。

    l当主数据库上的参数设置为FULL时,实例日志将为只读(Read-Only)表空间以及读写(Read-Write)表空间产生额外记录。

    lTYPICAL模式的性能开销约为510%,对于FULL模式可能更高。

    Data Guard一起使用时,丢失写入检测最有效。在这种情况下在主数据库和备用数据库中都设置了DB_LOST_WRITE_PROTECT。当备用数据库在托管恢复(Managed recovery)期间应用重做时,它会读取相应的块并将SCN与重做日志中的SCN进行比较。如果主数据库上的块SCN低于备用数据库上的块SCN,则它会检测主数据库上的写入丢失并引发外部错误(ORA-752)。如果SCN较高,则会检测到备用数据库上的写入丢失并引发内部错误(ORA-600 [3020])。在任何一种情况下,备用数据库都会在警报日志和跟踪文件中写入失败的原因。
    要修复主数据库上的丢失写入,可以通过备库的Failover,或者通过备库获取完整的数据文件进行恢复;要修复备用数据库上的丢失写入,必须重新创建整个备用数据库或还原受影响文件的备份。
    即便不使用Data Guard时,启用丢失写入检测也很有用。在这种情况下,可能在两种情形遇到丢失写入:在正常数据库操作期间在介质恢复期间。在第一种情况下,没有直接的方法来检测错误,但是可以通过将备份还原到相应的疑点位置进行判断,将数据库或表空间恢复到相应的SCN,就会触发丢失写入错误(ORA-752)。如果在介质恢复期间遇到丢失的写入错误,则需要使用RESETLOGS选项打开数据库,数据库是已知的,但是会导致Resetlogs SCN之后的数据被抛弃;如果数据库的备份能追溯到完好的备份,则可以执行顺序的恢复,但是如果备份(数据文件和归档日志)都发生在写丢失之后,则意味着写丢失无法挽回。
    通过以下测试,来验证一下数据库在这一特性之下的表征。我们在主库和备库同时设置
      SQL> select banner from v$version;
      BANNER
      ---------------------------------------------------------------
      Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production
      SQL> alter system set db_lost_write_protect='TYPICAL';
      System altered.
      备库设置:
        SQL> select open_mode from v$database;
        OPEN_MODE
        --------------------
        READ ONLY WITH APPLY
        SQL> alter system set db_lost_write_protect='TYPICAL';
        System altered.
        来看一个测试:
          SQL> create tablespace lostwrite datafile size 10M;
          Tablespace created.
          SQL> create user enmo identified by enmo default tablespace lostwrite;
          User created.
          SQL> grant connect,resource,dba to enmo;
          Grant succeeded.
          SQL> connect enmo/enmo
          SQL> create table enmotech (id number);
          Table created.
          SQL> insert into enmotech values(200);
          1 row created.
          SQL> commit;
          Commit complete.
          SQL> alter system flush buffer_cache;
          System altered.
          SQL> alter system switch logfile;
          System altered.

          此时备份数据文件,此时的数据文件中,包含了 200 的数据:
            [oracle@DEVDB datafile]$ cp o1_mf_lostwrit_g47tb4vg_.dbf o1_mf_lostwrit_g47tb4vg_.dbf.old

            然后更新信息:
              SQL> update enmotech set id=888;
              1 row updated.
              SQL> commit;
              Commit complete.
              SQL> select * from enmotech;
              ID
              ----------
              888
              SQL> alter system flush buffer_cache;
              System altered.
              此时备库已经能够读取更新后的数据:
                SQL> select * from enmo.enmotech;
                ID
                ----------
                888

                注意此时在主库,用备份文件覆盖原始文件(也可以通过dd只覆盖数据块部分)
                  [oracle@DEVDB datafile]$ cp o1_mf_lostwrit_g47tb4vg_.dbf.old o1_mf_lostwrit_g47tb4vg_.dbf

                  主库执行查询,返回值变成了200,这就模拟了一次写丢失:
                    SQL> select * from enmotech;
                    ID
                    ----------
                    200

                    现在来检查一下主备库的告警日志,看看数据库发现了什么,以下是主库的信息,一切正常没有任何错误:
                      2019-01-20T11:21:40.836426+08:00
                      create tablespace lostwrite datafile size 10M
                      2019-01-20T11:21:41.725876+08:00
                      Control autobackup written to DISK device
                      handle '/fast_recovery_area/DB18C/autobackup/2019_01_20/o1_mf_s_998047301_g47tb5mb_.bkp'
                      Completed: create tablespace lostwrite datafile size 10M
                      2019-01-20T11:23:30.535149+08:00
                      ALTER SYSTEM: Flushing buffer cache inst=0 container=0 global
                      2019-01-20T11:23:44.006236+08:00
                      Thread 1 advanced to log sequence 1722 (LGWR switch)
                      Current log# 1 seq# 1722 mem# 0: DB18C/onlinelog/o1_mf_1_f9ovsb94_.log
                      2019-01-20T11:23:44.087462+08:00
                      ARC1 (PID:23353): Archived Log entry 153 added for T-1.S-1721 ID 0x3926be67 LAD:1
                      2019-01-20T11:23:44.125533+08:00
                      TT03 (PID:7880): SRL selected for T-1.S-1722 for LAD:2
                      2019-01-20T11:25:39.365092+08:00
                      ALTER SYSTEM: Flushing buffer cache inst=0 container=0 global

                      接下来看看备库信息,备库已经抛出了异常,提示主库发生了写丢失,停止了数据库恢复,请求人工介入处理:
                        2019-01-20T11:26:03.408260+08:00
                        Errors in file db18c_s/DB18C_S/trace/DB18C_S_mz00_28990.trc:
                        ORA-00312: online log 2 thread 1: '/DB18C/onlinelog/o1_mf_2_f9ovsbj1_.log'
                        ORA-27037: unable to obtain file status
                        Linux-x86_64 Error: 2: No such file or directory
                        Additional information: 7
                        Standby redo application has detected that the primary database lost a disk write.
                        No redo at or after SCN 0x0000000015734233 can be used for recovery.
                        BLOCK THAT LOST WRITE 133, FILE 2, TABLESPACE# 7
                        The block read during the normal successful database operation had SCN 359874679 (0x0000000015734077) seq 1 (0x01)
                        ERROR: ORA-00752 detected lost write on primary
                        Slave exiting with ORA-752 exception
                        2019-01-20T11:26:03.555719+08:00
                        Recovery Slave PR02 previously exited with exception 752
                        2019-01-20T11:26:04.501889+08:00
                        Background Media Recovery process shutdown (DB18C_S)
                        这就是Oracle数据库在防止数据损坏上不断的改进。当备库检测到错误之后,会停止恢复,直到问题解决:
                          2019-01-20T11:26:04.442236+08:00
                          PR00 (PID:28727): MRP0: Background Media Recovery terminated with error 448
                          2019-01-20T11:26:04.442855+08:00
                          Errors in file db18c_s/DB18C_S/trace/DB18C_S_pr00_28727.trc:
                          ORA-00448: normal completion of background process
                          PR00 (PID:28727): Managed Standby Recovery not using Real Time Apply
                          Recovery interrupted!
                          主库的错误检测在两天后发出警告,提示这个数据文件属于旧版本,出现了介质失败:
                            2019-01-22T13:22:27.415907+08:00
                            Errors in file db18c/DB18C/trace/DB18C_ckpt_20835.trc:
                            ORA-63999: data file suffered media failure
                            ORA-01122: database file 2 failed verification check
                            ORA-01110: data file 2: '/DB18C/datafile/o1_mf_lostwrit_g47tb4vg_.dbf'
                            ORA-01208: data file is an old version - not accessing current version

                            在主库执行恢复时,会抛出异常,提示写丢失错误被检测到:
                              SQL> recover datafile 2;
                              ORA-00279: change 359874547 generated at01/20/2019 11:21:40 needed for thread 1
                              ORA-00289: suggestion : /DB18C/archivelog/2019_01_20/o1_mf_1_1721_g47tg02b_.arc
                              ORA-00280: change 359874547 for thread 1 is in sequence #1721
                              ORA-00283: recovery session canceled due to errors
                              ORA-00752: recovery detected a lost write of a data block
                              ORA-10567: Redo is inconsistent with data block (file# 2, block# 133, file offset is 1089536 bytes)
                              ORA-10564: tablespace LOSTWRITE
                              ORA-01110: data file 2: '/DB18C/datafile/o1_mf_lostwrit_g47tb4vg_.dbf'
                              ORA-10561: block type 'TRANSACTION MANAGED DATA BLOCK', data object# 186211

                              此时可以在主库复制备库的数据文件,关闭写丢失检测(否则无法执行不一致数据文件的恢复),然后执行恢复:
                                SQL> alter system set db_lost_write_protect=none;
                                System altered.
                                RMAN> recover datafile 2;
                                Starting recover at2019-01-22 13:47:50
                                using target database control file instead of recovery catalog
                                allocated channel: ORA_DISK_1
                                channel ORA_DISK_1: SID=151 device type=DISK
                                starting media recovery
                                archived log for thread 1 with sequence 1722 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1722_g4fb3ghj_.arc
                                archived log for thread 1 with sequence 1723 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1723_g4fb442h_.arc
                                archived log for thread 1 with sequence 1724 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1724_g4fbdlcq_.arc
                                archived log for thread 1 with sequence 1725 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1725_g4fbg000_.arc
                                archived log for thread 1 with sequence 1726 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1726_g4fbh4wv_.arc
                                archived log for thread 1 with sequence 1727 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1727_g4fbj7hc_.arc
                                archived log for thread 1 with sequence 1728 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1728_g4fcmbvx_.arc
                                archived log file name=/archivelog/2019_01_22/o1_mf_1_1722_g4fb3ghj_.arc thread=1 sequence=1722
                                archived log file name=/archivelog/2019_01_22/o1_mf_1_1723_g4fb442h_.arc thread=1 sequence=1723
                                archived log file name=/archivelog/2019_01_22/o1_mf_1_1724_g4fbdlcq_.arc thread=1 sequence=1724
                                archived log file name=/archivelog/2019_01_22/o1_mf_1_1725_g4fbg000_.arc thread=1 sequence=1725
                                media recovery complete, elapsed time: 00:00:05
                                Finished recover at2019-01-22 13:47:57
                                RMAN> alter database datafile 2 online;
                                Statement processed
                                这就是这个丢失检测验证的完整过程,也就是Oracle实现写丢失检测的方法。
                                【Oracle 12.2 版本的写丢失检测增强】



                                Oracle 12c Release 2中引入了一项新功能Shadow Lost Write Protection,它可以在主数据库中进行丢失写检测,而无需Oracle Data Guard备用数据库。即使配置了Dataguard备用数据库,此功能也非常有价值,任何SELECT / DML都会在读取写丢失物理块时触发错误并记录。这更有利于指导DBA通过介质恢复来恢复块(或它的数据文件/表空间)。
                                在上一个测试的基础上,再继续深入。首先创建一个用于记录块信息的表空间,这个表空间的类型必须是BIGFILE表空间:
                                  SQL> create bigfile tablespace shadow_tbs datafile '/DB18C/datafile/shadow_tbs.dbf' size 200M lost write protection;
                                  Tablespace created.
                                  SQL> select CONTENTS from DBA_TABLESPACES where TABLESPACE_NAME='SHADOW_TBS';
                                  CONTENTS
                                  ---------------------
                                  LOST WRITE PROTECTION
                                  然后需要在数据库级别启用丢失写检测,并且在需要保护的数据表空间启用监控:
                                    SQL> alter database enable lost write protection;
                                    Database altered.
                                    SQL> alter tablespace lostwrite enable lost write protection;
                                    Tablespace altered.
                                    SQL> select * from database_properties where property_name ='NEW_LOST_WRITE';
                                    PROPERTY_NAME PROPERTY_V DESCRIPTION
                                    -------------------- ---------- ------------------------------
                                    NEW_LOST_WRITE TRUE new lost write protection
                                    SQL> select LOST_WRITE_PROTECT from DBA_TABLESPACES where TABLESPACE_NAME='LOSTWRITE';
                                    LOST_WR
                                    -------
                                    ENABLED
                                    首先确认当前数据存储的数据块,表中有四条数据:
                                      SQL> create index idxid on enmotech(id);
                                      Index created.
                                      SQL> select DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid) BLOCK#, DBMS_ROWID.ROWID_RELATIVE_FNO(rowid) RFILE#,a.* from enmo.enmotech a;
                                      BLOCK# RFILE# ID
                                      ---------- ---------- ----------
                                      132 2 999
                                      133 2 888
                                      133 2 888
                                      134 2 666
                                      分别对这几个数据块进行备份,以下是备份132号数据块(注意,由于操作系统保留一个数据块,所以dd时跳过132个数据块):
                                        [oracle@DEVDB datafile]$ dd if=o1_mf_lostwrit_g47tb4vg_.dbf bs=8192 count=1 skip=132 of=bakblk132.dmp
                                        1+0 records in
                                        1+0 records out
                                        8192 bytes (8.2 kB) copied, 0.000160311 s, 51.1 MB/s
                                        然后进行数据修改,提交:
                                          SQL> insert into enmotech values(777);
                                          1 row created.
                                          SQL> commit;
                                          Commit complete.
                                          SQL> select DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid) BLOCK#, DBMS_ROWID.ROWID_RELATIVE_FNO(rowid) RFILE#,a.* from enmo.enmotech a;
                                          BLOCK# RFILE# ID
                                          ---------- ---------- ----------
                                          132 2 999
                                          132 2 777
                                          133 2 888
                                          133 2 888
                                          134 2 666
                                          现在可以关闭数据库,恢复备份数据块:
                                            [oracle@DEVDB datafile]$ dd of=o1_mf_lostwrit_g47tb4vg_.dbf bs=8192 count=1 seek=132 if=bakblk132.dmp conv=notrunc
                                            1+0 records in
                                            1+0 records out
                                            8192 bytes (8.2 kB) copied, 0.000212014 s, 38.6 MB/s


                                            注意,这一步是关键,相当于偷梁换柱,将原有的一个Block替换掉,这是通过操作系统完成的,未经过数据库内部的DML修改操作。

                                            现在启动数据库,进行查询,数据库立刻检测到了写丢失:
                                              SQL> connect enmo/enmo
                                              Connected.
                                              SQL> select * from enmotech;
                                              select * from enmotech
                                              *
                                              ERROR at line 1:
                                              ORA-65478: shadow lost write protection - found lost write
                                              告警日志中也会记录:
                                                2019-02-10T22:25:14.776576+08:00
                                                ERROR - I/O type:buffered I/O found lost write in block with file#:2 rdba:0x800084, Expected SCN:0x0000000015bd951a SCN in block:0x0000000015bd84e6, approx current SCN:0x0000000015bdab52, RAC instance:1 pdb:0
                                                如果此时禁用写丢失保护,查询可以执行,输出了4条记录,值为『777』的数据丢失了:
                                                  SQL> alter database disable lost write protection;
                                                  Database altered.
                                                  SQL> select * from enmotech;
                                                  ID
                                                  ----------
                                                  999
                                                  888
                                                  888
                                                  666
                                                  但是由于之前创建了索引,使用索引的查询,仍然能够输出这个丢失的信息,事实上索引和数据已经不一致了:
                                                    SQL> select * from enmotech where id=777;
                                                    ID
                                                    ----------
                                                    777
                                                    Execution Plan
                                                    ----------------------------------------------------------
                                                    Plan hash value: 3880444692
                                                    --------------------------------------------------------------------------
                                                    | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
                                                    --------------------------------------------------------------------------
                                                    | 0 | SELECT STATEMENT | | 1 | 4 | 1 (0)| 00:00:01 |
                                                    |* 1 | INDEX RANGE SCAN| IDXID | 1 | 4 | 1 (0)| 00:00:01 |
                                                    --------------------------------------------------------------------------
                                                    Oracle的这个特性进一步的增强了Oracle数据库的写丢失预防功能,当及时检测到问题后,可以通过块恢复(block recovery)进行数据块级别的修复,快速消除写丢失的影响
                                                    【小结】


                                                    今天,国产数据库时代已经到来,大量国产数据库产品正在不断增强其易用性和安全性,而在这些方面,经过广泛应用考验的传统数据库就成为了很好的学习对象。
                                                    Oracle数据库从控制文件校验、数据块校验,再到专用的写丢失特性校验,已经做出了大量探索,这些探索值得我们去学习、验证、借鉴。



                                                    云和恩墨大讲堂 | 一个分享交流的地方 

                                                    长按,识别二维码,加入万人交流社群


                                                    请备注:云和恩墨大讲堂

                                                      点个“在看” 
                                                    你的喜欢会被看到❤

                                                    文章转载自数据和云,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                    评论