/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    hivechek.c

Abstract:

    This module implements consistency checking for hives.

Author:

    Bryan M. Willman (bryanwi) 09-Dec-91

Environment:


Revision History:

--*/

#include    "cmp.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,HvCheckHive)
#pragma alloc_text(PAGE,HvCheckBin)
#endif

//
// debug structures
//
extern struct {
    PHHIVE      Hive;
    ULONG       Status;
    ULONG       Space;
    HCELL_INDEX MapPoint;
    PHBIN       BinPoint;
} HvCheckHiveDebug;

extern struct {
    PHBIN       Bin;
    ULONG       Status;
    PHCELL      CellPoint;
} HvCheckBinDebug;


#if DBG
ULONG HvHiveChecking=0;
#endif

ULONG
HvCheckHive(
    PHHIVE  Hive,
    PULONG  Storage OPTIONAL
    )
/*++

Routine Description:

    Check the consistency of a hive.  Apply CheckBin to bins, make sure
    all pointers in the cell map point to correct places.

Arguments:

    Hive - supplies a pointer to the hive control structure for the
            hive of interest.

    Storage - supplies adddress of ULONG to receive size of allocated user data

Return Value:

    0 if Hive is OK.  Error return indicator if not.  Error value
    comes from one of the check procedures.

    RANGE:  2000 - 2999

--*/
{
    HCELL_INDEX p;
    ULONG       Length;
    ULONG       localstorage = 0;
    PHMAP_ENTRY t;
    PHBIN       Bin;
    ULONG   i;
    ULONG   rc;
    PFREE_HBIN  FreeBin;

    HvCheckHiveDebug.Hive = Hive;
    HvCheckHiveDebug.Status = 0;
    HvCheckHiveDebug.Space = (ULONG)-1;
    HvCheckHiveDebug.MapPoint = HCELL_NIL;
    HvCheckHiveDebug.BinPoint = 0;

    p = 0;

#ifdef CM_MAP_NO_READ
#ifndef _CM_LDR_
    //
    // we need to make sure all the cell's data is faulted in inside a 
    // try/except block, as the IO to fault the data in can throw exceptions
    // STATUS_INSUFFICIENT_RESOURCES, in particular
    //

    try {
#endif //_CM_LDR_
#endif //CM_MAP_NO_READ

        //
        // one pass for Stable space, one pass for Volatile
        //
        for (i = 0; i <= Volatile; i++) {
            Length = Hive->Storage[i].Length;

            //
            // for each bin in the space
            //
            while (p < Length) {
                t = HvpGetCellMap(Hive, p);
                if (t == NULL) {
                    CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckHive:"));
                    CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"\tBin@:%p invalid\n", Bin));
                    HvCheckHiveDebug.Status = 2005;
                    HvCheckHiveDebug.Space = i;
                    HvCheckHiveDebug.MapPoint = p;
                    return 2005;
                }

            
                if( (t->BinAddress & (HMAP_INPAGEDPOOL|HMAP_INVIEW)) == 0) {
                    //
                    // view is not mapped, neither in paged pool
                    // try to map it.
                    //
                
                    // volatile info is always in paged pool
                    ASSERT( i == Stable );

                    if( !NT_SUCCESS(CmpMapThisBin((PCMHIVE)Hive,p,FALSE)) ) {
                        //
                        // we cannot map this bin due to insufficient resources. 
                        //
                        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckHive:"));
                        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"\tinsufficient resources while mapping Bin@:%p\n", Bin));
                        HvCheckHiveDebug.Status = 2006;
                        HvCheckHiveDebug.Space = i;
                        HvCheckHiveDebug.MapPoint = p;
                        return 2010;
                    }
                }

                if ((t->BinAddress & HMAP_DISCARDABLE) == 0) {

                    Bin = (PHBIN)HBIN_BASE(t->BinAddress);

                    //
                    // bin header valid?
                    //
                    if ( (Bin->Size > Length)                           ||
                         (Bin->Signature != HBIN_SIGNATURE)             ||
                         (Bin->FileOffset != p)
                       )
                    {
                        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckHive:"));
                        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"\tBin@:%p invalid\n", Bin));
                        HvCheckHiveDebug.Status = 2010;
                        HvCheckHiveDebug.Space = i;
                        HvCheckHiveDebug.MapPoint = p;
                        HvCheckHiveDebug.BinPoint = Bin;
                        return 2010;
                    }

                    //
                    // structure inside the bin valid?
                    //
                    rc = HvCheckBin(Hive, Bin, &localstorage);
                    if (rc != 0) {
                        HvCheckHiveDebug.Status = rc;
                        HvCheckHiveDebug.Space = i;
                        HvCheckHiveDebug.MapPoint = p;
                        HvCheckHiveDebug.BinPoint = Bin;
                        return rc;
                    }

                    p = (ULONG)p + Bin->Size;

                } else {
                    //
                    // Bin is not present, skip it and advance to the next one.
                    //
                    FreeBin = (PFREE_HBIN)t->BlockAddress;
                    p+=FreeBin->Size;
                }
            }

            p = 0x80000000;     // Beginning of Volatile space
        }

#ifdef CM_MAP_NO_READ
#ifndef _CM_LDR_
    } except (EXCEPTION_EXECUTE_HANDLER) {
        HvCheckHiveDebug.Status = 2015;
        HvCheckHiveDebug.Space = GetExceptionCode();
        return HvCheckHiveDebug.Status;
    }
#endif //_CM_LDR_
#endif //CM_MAP_NO_READ

    if (ARGUMENT_PRESENT(Storage)) {
        *Storage = localstorage;
    }
    return 0;
}


ULONG
HvCheckBin(
    PHHIVE  Hive,
    PHBIN   Bin,
    PULONG  Storage
    )
/*++

Routine Description:

    Step through all of the cells in the bin.  Make sure that
    they are consistent with each other, and with the bin header.

Arguments:

    Hive - pointer to the hive control structure

    Bin - pointer to bin to check

    Storage - pointer to a ulong to get allocated user data size

Return Value:

    0 if Bin is OK.  Number of test in procedure that failed if not.

    RANGE:  1 - 1999

--*/
{
    PHCELL  p;
    PHCELL  np;
    PHCELL  lp;
    ULONG   freespace = 0L;
    ULONG   allocated = 0L;
    ULONG   userallocated = 0L;

    HvCheckBinDebug.Bin = Bin;
    HvCheckBinDebug.Status = 0;
    HvCheckBinDebug.CellPoint = 0;

    //
    // Scan all the cells in the bin, total free and allocated, check
    // for impossible pointers.
    //
    p = (PHCELL)((PUCHAR)Bin + sizeof(HBIN));
    lp = p;

    // DRAGOS:
    // The way allocated and freespace are computed implies the following invariants:
    // 1. allocated + freespace = p + p->Size - (Bin + sizeof(HBIN)). This is because p->Size is added either to allocated or to freespace.
    //    So, assuming that allocated > Bin->Size , then
    //              ==> p + p->Size - (Bin + sizeof(HBIN)) > Bin->Size.
    //              ==> p + p->Size > Bin + Bin->Size + sizeof(HBIN)
    //              ==> p + p->Size > Bin + Bin->Size
    //      This proves that the test "NeverFail 1" (see bellow) will never fail, because when something is wrong, the test above it (namely "Fail 1") will fail 
    //      and the function will exit.
    //
    //    The same logic applies to the test "NeverFail 2", so it can be removed also.
    //
    // 2. The new value of p is always calculated as p = p + p->Size. By the time this is done, the new value of p (ie. p + p->Size) is already checked against 
    //      Bin + Bin->Size (see tests "Fail 1" and "Fail 2"). So, if p > Bin + Bin->Size, either "Fail 1" or "Fail 2" will fail before asigning the new bogus value 
    //      to p. Therefore, the only possible path to exit the while loop (except a return 20 or return 40), is when p == Bin + Bin->Size.
    //      ==> test "NeverFail 3" can be removed as it will never fail !
    //
    // 3. Considering 1 (where p + p->Size became p) 
    //              ==> allocated + freespace =  p - (Bin + sizeof(HBIN))
    //    But, Considering 2 (above), when the while loop exits, p = Bin + Bin->Size
    //              ==> allocated + freespace = Bin + Bin->Size - (Bin + sizeof(HBIN))
    //              ==> allocated + freespace + sizeof(HBIN) = Bin->Size
    //       This proves that test "NeverFail 4" (see bellow) will never fail as the expresion tested is always true (if the flow of execution reaches the test point).
    //

    while (p < (PHCELL)((PUCHAR)Bin + Bin->Size)) {

        //
        // Check last pointer
        //
        if (USE_OLD_CELL(Hive)) {
            if (lp == p) {
                if (p->u.OldCell.Last != HBIN_NIL) {
                    CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckBin 20: First cell has wrong last pointer\n"));
                    CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"Bin = %p\n", Bin));
                    HvCheckBinDebug.Status = 20;
                    HvCheckBinDebug.CellPoint = p;
                    return 20;
                }
            } else {
                if ((PHCELL)(p->u.OldCell.Last + (PUCHAR)Bin) != lp) {
                    CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckBin 30: incorrect last pointer\n"));
                    CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"Bin = %p\n", Bin));
                    CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"p = %p\n", (ULONG_PTR)p));
                    HvCheckBinDebug.Status = 30;
                    HvCheckBinDebug.CellPoint = p;
                    return 30;
                }
            }
        }

        
        //
        // Check size
        //
        if (p->Size < 0) {

            //
            // allocated cell
            //

            // DRAGOS:    Fail 1
            // This test will alway fail prior to the failure of the bellow test
            //
            if ( ((ULONG)(p->Size * -1) > Bin->Size)        ||
                 ( (PHCELL)((p->Size * -1) + (PUCHAR)p) >
                   (PHCELL)((PUCHAR)Bin + Bin->Size) )
               )
            {
                CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckBin 40: impossible allocation\n"));
                CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"Bin = %p\n", Bin));
                HvCheckBinDebug.Status = 40;
                HvCheckBinDebug.CellPoint = p;
                return 40;
            }

            allocated += (p->Size * -1);
            if (USE_OLD_CELL(Hive)) {
                userallocated += (p->Size * -1) - FIELD_OFFSET(HCELL, u.OldCell.u.UserData);
            } else {
                userallocated += (p->Size * -1) - FIELD_OFFSET(HCELL, u.NewCell.u.UserData);
            }

            //
            // DRAGOS:   NeverFail 1
            // This test will never fail. If a size is wrong the above test (Fail 1)will fail. We can remove this test (it's useless).
            //
            if (allocated > Bin->Size) {
                CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckBin 50: allocated exceeds available\n"));
                CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"Bin = %p\n", Bin));
                HvCheckBinDebug.Status = 50;
                HvCheckBinDebug.CellPoint = p;
                return 50;
            }

            np = (PHCELL)((PUCHAR)p + (p->Size * -1));



        } else {

            //
            // free cell
            //

            // DRAGOS:    Fail 2
            // This test will alway fail prior to the failure of the bellow test
            //
            if ( ((ULONG)p->Size > Bin->Size)               ||
                 ( (PHCELL)(p->Size + (PUCHAR)p) >
                   (PHCELL)((PUCHAR)Bin + Bin->Size) ) ||
                 (p->Size == 0) )
            {
                CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckBin 60: impossible free block\n"));
                CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"Bin = %p\n", Bin));
                HvCheckBinDebug.Status = 60;
                HvCheckBinDebug.CellPoint = p;
                return 60;
            }

            freespace = freespace + p->Size;

            //
            // DRAGOS:   NeverFail 2
            // This test will never fail. If a size is wrong the above test (Fail 2) will fail. We can remove this test (it's useless).
            //
            if (freespace > Bin->Size) {
                CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckBin 70: free exceeds available\n"));
                CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"Bin = %p\n", Bin));
                HvCheckBinDebug.Status = 70;
                HvCheckBinDebug.CellPoint = p;
                return 70;
            }

            np = (PHCELL)((PUCHAR)p + p->Size);

        }

        lp = p;
        p = np;
    }

    // DRAGOS:  NeverFail 4
    // This test never fails. If the while loop exits, the condition tested here is always true!!!
    // We can remove this test (it's useless)
    //
    if ((freespace + allocated + sizeof(HBIN)) != Bin->Size) {
        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckBin 995: sizes do not add up\n"));
        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"Bin = %p\n", Bin));
        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"freespace = %08lx  ", freespace));
        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"allocated = %08lx  ", allocated));
        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"size = %08lx\n", Bin->Size));
        HvCheckBinDebug.Status = 995;
        return 995;
    }

    // DRAGOS:  NeverFail 3
    // This test never fails. The only way out of the while loop is when p == Bin + Bin->Size !!!!!!!
    // We can remove this test (it's useless)
    //
    if (p != (PHCELL)((PUCHAR)Bin + Bin->Size)) {
        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"HvCheckBin 1000: last cell points off the end\n"));
        CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"Bin = %p\n", Bin));
        HvCheckBinDebug.Status = 1000;
        return 1000;
    }

    if (ARGUMENT_PRESENT(Storage)) {
        *Storage += userallocated;
    }
    return 0;
}
