# CHIPSEC: Platform Security Assessment Framework
# Copyright (c) 2010-2021, Intel Corporation
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; Version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# Contact information:
# chipsec@intel.com
#


"""
CPU SMM Cache Poisoning / System Management Range Registers check

This module checks to see that SMRRs are enabled and configured.

Reference:
    Researchers demonstrated a way to use CPU cache to effectively change values in SMRAM in
    `Attacking SMM Memory via Intel CPU Cache Poisoning <http://www.invisiblethingslab.com/resources/misc09/smm_cache_fun.pdf>`_
    and `Getting into the SMRAM: SMM Reloaded <http://cansecwest.com/csw09/csw09-duflot.pdf>`_ .
    If ring 0 software can make SMRAM cacheable and then populate cache lines at SMBASE with exploit code,
    then when an SMI is triggered, the CPU could execute the exploit code from cache.
    System Management Mode Range Registers (SMRRs) force non-cachable behavior and block access to SMRAM when the CPU is not in SMM.
    These registers need to be enabled/configured by the BIOS.

Usage:
    ``chipsec_main -m common.smrr [-a modify]``

    - ``-a modify``: Attempt to modify memory at SMRR base

Examples:
    >>> chipsec_main.py -m common.smrr
    >>> chipsec_main.py -m common.smrr -a modify

Registers used:
    - IA32_SMRR_PHYSBASE.PhysBase
    - IA32_SMRR_PHYSBASE.Type
    - IA32_SMRR_PHYSMASK.PhysMask
    - IA32_SMRR_PHYSMASK.Valid

"""

from chipsec.module_common import BaseModule, BIOS, SMM, OPT_MODIFY
from chipsec.library.returncode import ModuleResult
from chipsec.hal.msr import MemType
from typing import List

TAGS = [BIOS, SMM]
METADATA_TAGS = ['OPENSOURCE', 'IA', 'COMMON', 'SMRR']


class smrr(BaseModule):

    def __init__(self):
        BaseModule.__init__(self)

    def is_supported(self) -> bool:
        mtrr_exist = self.cs.register.is_defined('MTRRCAP')
        pbase_exist = self.cs.register.is_defined('IA32_SMRR_PHYSBASE')
        pmask_exist = self.cs.register.is_defined('IA32_SMRR_PHYSMASK')
        if mtrr_exist and pbase_exist and pmask_exist:
            return True
        self.logger.log_information('Required registers are not defined for this platform.  Skipping module.')
        return False

    def check_SMRR(self, do_modify: bool) -> int:

        if self.cs.cpu.check_SMRR_supported():
            self.logger.log_good('OK. SMRR range protection is supported')
        else:
            self.logger.log_not_applicable('CPU does not support SMRR range protection of SMRAM')
            self.result.setStatusBit(self.result.status.NOT_APPLICABLE)
            self.res = self.result.getReturnCode(ModuleResult.NOTAPPLICABLE)

        smrr_ok = True

        self.logger.log('')
        self.logger.log('[*] Checking SMRR range base programming..')
        msr_smrrbase = self.cs.register.read('IA32_SMRR_PHYSBASE')
        self.cs.register.print('IA32_SMRR_PHYSBASE', msr_smrrbase)
        smrrbase = self.cs.register.get_field('IA32_SMRR_PHYSBASE', msr_smrrbase, 'PhysBase', True)
        smrrtype = self.cs.register.get_field('IA32_SMRR_PHYSBASE', msr_smrrbase, 'Type')
        self.logger.log(f'[*] SMRR range base: 0x{smrrbase:016X}')

        if smrrtype in MemType:
            self.logger.log(f'[*] SMRR range memory type is {MemType[smrrtype]}')
        else:
            smrr_ok = False
            self.logger.log_bad(f'SMRR range memory type 0x{smrrtype:X} is invalid')

        if 0 == smrrbase:
            smrr_ok = False
            self.logger.log_bad('SMRR range base is not programmed')

        if smrr_ok:
            self.logger.log_good('OK so far. SMRR range base is programmed')

        self.logger.log('')
        self.logger.log('[*] Checking SMRR range mask programming..')
        msr_smrrmask = self.cs.register.read('IA32_SMRR_PHYSMASK')
        self.cs.register.print('IA32_SMRR_PHYSMASK', msr_smrrmask)
        smrrmask = self.cs.register.get_field('IA32_SMRR_PHYSMASK', msr_smrrmask, 'PhysMask', True)
        smrrvalid = self.cs.register.get_field('IA32_SMRR_PHYSMASK', msr_smrrmask, 'Valid')
        self.logger.log(f'[*] SMRR range mask: 0x{smrrmask:016X}')

        if not (smrrvalid and (0 != smrrmask)):
            smrr_ok = False
            self.logger.log_bad('SMRR range is not enabled')

        if smrr_ok:
            self.logger.log_good('OK so far. SMRR range is enabled')

        self.logger.log('')
        self.logger.log('[*] Verifying that SMRR range base & mask are the same on all logical CPUs..')
        for tid in range(self.cs.msr.get_cpu_thread_count()):
            msr_base = self.cs.register.read('IA32_SMRR_PHYSBASE', tid)
            msr_mask = self.cs.register.read('IA32_SMRR_PHYSMASK', tid)
            self.logger.log(f'[CPU{tid:d}] SMRR_PHYSBASE = {msr_base:016X}, SMRR_PHYSMASK = {msr_mask:016X}')
            if (msr_base != msr_smrrbase) or (msr_mask != msr_smrrmask):
                smrr_ok = False
                self.logger.log_bad('SMRR range base/mask do not match on all logical CPUs')
                break

        if smrr_ok:
            self.logger.log_good('OK so far. SMRR range base/mask match on all logical CPUs')

        self.logger.log(f'[*] Trying to read memory at SMRR base 0x{smrrbase:08X}...')

        ok = 0xFFFFFFFF == self.cs.mem.read_physical_mem_dword(smrrbase)
        smrr_ok = smrr_ok and ok
        if ok:
            self.logger.log_passed('SMRR reads are blocked in non-SMM mode')
        else:
            self.logger.log_failed('SMRR reads are not blocked in non-SMM mode')

        if (do_modify):
            self.logger.log(f'[*] Trying to modify memory at SMRR base 0x{smrrbase:08X}...')
            self.cs.mem.write_physical_mem_dword(smrrbase, 0x90909090)
            ok = 0x90909090 != self.cs.mem.read_physical_mem_dword(smrrbase)
            smrr_ok = smrr_ok and ok
            if ok:
                self.logger.log_good('SMRR writes are blocked in non-SMM mode')
            else:
                self.logger.log_bad('SMRR writes are not blocked in non-SMM mode')

        self.logger.log('')
        if not smrr_ok:
            res = ModuleResult.FAILED
            self.result.setStatusBit(self.result.status.CONFIGURATION)
            self.logger.log_failed('SMRR protection against cache attack is not configured properly')
        else:
            res = ModuleResult.PASSED
            self.logger.log_passed('SMRR protection against cache attack is properly configured')

        return self.result.getReturnCode(res)

    def run(self, module_argv: List[str]) -> int:
        self.logger.start_test('CPU SMM Cache Poisoning / System Management Range Registers')
        do_modify = (len(module_argv) > 0) and (module_argv[0] == OPT_MODIFY)
        self.res = self.check_SMRR(do_modify)
        return self.res
