/* * Copyright (c) 2021-2022 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #define NVRAM_CHRP_APPLE_HEADER_NAME_V1 "nvram" #define NVRAM_CHRP_APPLE_HEADER_NAME_V2 "2nvram" #define NVRAM_CHRP_PARTITION_NAME_COMMON_V1 "common" #define NVRAM_CHRP_PARTITION_NAME_SYSTEM_V1 "system" #define NVRAM_CHRP_PARTITION_NAME_COMMON_V2 "2common" #define NVRAM_CHRP_PARTITION_NAME_SYSTEM_V2 "2system" #define NVRAM_CHRP_LENGTH_BLOCK_SIZE 0x10 // CHRP length field is in 16 byte blocks typedef struct chrp_nvram_header { //16 bytes uint8_t sig; uint8_t cksum; // checksum on sig, len, and name uint16_t len; // total length of the partition in 16 byte blocks starting with the signature // and ending with the last byte of data area, ie len includes its own header size char name[12]; uint8_t data[0]; } chrp_nvram_header_t; typedef struct apple_nvram_header { // 16 + 16 bytes struct chrp_nvram_header chrp; uint32_t adler; uint32_t generation; uint8_t padding[8]; } apple_nvram_header_t; typedef struct { NVRAMPartitionType type; uint32_t offset; uint32_t size; } NVRAMRegionInfo; class IONVRAMCHRPHandler : public IODTNVRAMFormatHandler, IOTypedOperatorsMixin { private: bool _newData; bool _reload; IONVRAMController *_nvramController; IODTNVRAM *_provider; NVRAMVersion _version; uint32_t _generation; uint8_t *_nvramImage; uint32_t _commonPartitionOffset; uint32_t _commonPartitionSize; uint32_t _systemPartitionOffset; uint32_t _systemPartitionSize; OSSharedPtr &_varDict; uint32_t _commonUsed; uint32_t _systemUsed; uint32_t findCurrentBank(uint32_t *gen); IOReturn unserializeImage(const uint8_t *image, IOByteCount length); IOReturn serializeVariables(void); IOReturn reloadInternal(void); IOReturn setVariableInternal(const uuid_t varGuid, const char *variableName, OSObject *object); static OSSharedPtr unescapeBytesToData(const uint8_t *bytes, uint32_t length); static OSSharedPtr escapeDataToData(OSData * value); static bool convertPropToObject(const uint8_t *propName, uint32_t propNameLength, const uint8_t *propData, uint32_t propDataLength, LIBKERN_RETURNS_RETAINED const OSSymbol **propSymbol, LIBKERN_RETURNS_RETAINED OSObject **propObject); static bool convertPropToObject(const uint8_t *propName, uint32_t propNameLength, const uint8_t *propData, uint32_t propDataLength, OSSharedPtr& propSymbol, OSSharedPtr& propObject); static bool convertObjectToProp(uint8_t *buffer, uint32_t *length, const OSSymbol *propSymbol, OSObject *propObject); static bool convertObjectToProp(uint8_t *buffer, uint32_t *length, const char *propSymbol, OSObject *propObject); public: virtual ~IONVRAMCHRPHandler() APPLE_KEXT_OVERRIDE; IONVRAMCHRPHandler(OSSharedPtr &varDict); static bool isValidImage(const uint8_t *image, IOByteCount length); static IONVRAMCHRPHandler *init(IODTNVRAM *provider, const uint8_t *image, IOByteCount length, OSSharedPtr &varDict); virtual IOReturn unserializeVariables(void) APPLE_KEXT_OVERRIDE; virtual IOReturn setVariable(const uuid_t varGuid, const char *variableName, OSObject *object) APPLE_KEXT_OVERRIDE; virtual bool setController(IONVRAMController *controller) APPLE_KEXT_OVERRIDE; virtual IOReturn sync(void) APPLE_KEXT_OVERRIDE; virtual IOReturn flush(const uuid_t guid, IONVRAMOperation op) APPLE_KEXT_OVERRIDE; virtual void reload(void) APPLE_KEXT_OVERRIDE; virtual uint32_t getGeneration(void) const APPLE_KEXT_OVERRIDE; virtual uint32_t getVersion(void) const APPLE_KEXT_OVERRIDE; virtual uint32_t getSystemUsed(void) const APPLE_KEXT_OVERRIDE; virtual uint32_t getCommonUsed(void) const APPLE_KEXT_OVERRIDE; virtual bool getSystemPartitionActive(void) const APPLE_KEXT_OVERRIDE; }; static const char * get_bank_version_string(int version) { switch (version) { case kNVRAMVersion1: return NVRAM_CHRP_APPLE_HEADER_NAME_V1; case kNVRAMVersion2: return NVRAM_CHRP_APPLE_HEADER_NAME_V2; default: return "Unknown"; } } static uint32_t adler32(const uint8_t *buffer, size_t length) { uint32_t offset; uint32_t adler, lowHalf, highHalf; lowHalf = 1; highHalf = 0; for (offset = 0; offset < length; offset++) { if ((offset % 5000) == 0) { lowHalf %= 65521L; highHalf %= 65521L; } lowHalf += buffer[offset]; highHalf += lowHalf; } lowHalf %= 65521L; highHalf %= 65521L; adler = (highHalf << 16) | lowHalf; return adler; } static uint32_t nvram_get_adler(uint8_t *buf, int version) { return ((struct apple_nvram_header *)buf)->adler; } static uint32_t adler32_with_version(const uint8_t *buf, size_t len, int version) { size_t offset; switch (version) { case kNVRAMVersion1: case kNVRAMVersion2: offset = offsetof(struct apple_nvram_header, generation); break; default: return 0; } return adler32(buf + offset, len - offset); } static uint8_t chrp_checksum(const struct chrp_nvram_header *hdr) { uint16_t sum; const uint8_t *p; const uint8_t *begin = (const uint8_t *)hdr + offsetof(struct chrp_nvram_header, len); const uint8_t *end = (const uint8_t *)hdr + offsetof(struct chrp_nvram_header, data); // checksum the header (minus the checksum itself) sum = hdr->sig; for (p = begin; p < end; p++) { sum += *p; } while (sum > 0xff) { sum = (sum & 0xff) + (sum >> 8); } return sum & 0xff; } static IOReturn nvram_validate_header_v1v2(const uint8_t * buf, uint32_t *generation, int version) { IOReturn result = kIOReturnError; uint8_t checksum; const char *header_string = get_bank_version_string(version); struct chrp_nvram_header *chrp_header = (struct chrp_nvram_header *)buf; uint32_t local_gen = 0; require(buf != nullptr, exit); // Recovery Mode [Internal Build] 18D52-->18E141 [J307/308 Only] // we can only compare the first "nvram" parts of the name as some devices have additional junk from // a previous build likely copying past bounds of the "nvram" name in the const section if (memcmp(header_string, chrp_header->name, strlen(header_string)) == 0) { checksum = chrp_checksum(chrp_header); if (checksum == chrp_header->cksum) { result = kIOReturnSuccess; local_gen = ((struct apple_nvram_header*)buf)->generation; DEBUG_INFO("Found %s gen=%u\n", header_string, local_gen); if (generation) { *generation = local_gen; } } else { DEBUG_INFO("invalid checksum in header, found %#02x, expected %#02x\n", chrp_header->cksum, checksum); } } else { DEBUG_INFO("invalid bank for \"%s\", name = %#02x %#02x %#02x %#02x\n", header_string, chrp_header->name[0], chrp_header->name[1], chrp_header->name[2], chrp_header->name[3]); } exit: return result; } static void nvram_set_apple_header(uint8_t *buf, size_t len, uint32_t generation, int version) { if (version == kNVRAMVersion1 || version == kNVRAMVersion2) { struct apple_nvram_header *apple_hdr = (struct apple_nvram_header *)buf; generation += 1; apple_hdr->generation = generation; apple_hdr->adler = adler32_with_version(buf, len, version); } } static NVRAMVersion validateNVRAMVersion(const uint8_t *buf, size_t len, uint32_t *generation) { NVRAMVersion version = kNVRAMVersionUnknown; if (nvram_validate_header_v1v2(buf, generation, kNVRAMVersion1) == kIOReturnSuccess) { version = kNVRAMVersion1; goto exit; } if (nvram_validate_header_v1v2(buf, generation, kNVRAMVersion2) == kIOReturnSuccess) { version = kNVRAMVersion2; goto exit; } DEBUG_INFO("Unable to determine version\n"); exit: DEBUG_INFO("version=%u\n", version); return version; } IONVRAMCHRPHandler::~IONVRAMCHRPHandler() { } bool IONVRAMCHRPHandler::isValidImage(const uint8_t *image, IOByteCount length) { return validateNVRAMVersion(image, length, nullptr) != kNVRAMVersionUnknown; } IONVRAMCHRPHandler* IONVRAMCHRPHandler::init(IODTNVRAM *provider, const uint8_t *image, IOByteCount length, OSSharedPtr &varDict) { bool propertiesOk; IONVRAMCHRPHandler *handler = new IONVRAMCHRPHandler(varDict); handler->_provider = provider; propertiesOk = handler->getNVRAMProperties(); require_action(propertiesOk, exit, DEBUG_ERROR("Unable to get NVRAM properties\n")); require_action(length == handler->_bankSize, exit, DEBUG_ERROR("length 0x%llx != _bankSize 0x%x\n", length, handler->_bankSize)); if ((image != nullptr) && (length != 0)) { if (handler->unserializeImage(image, length) != kIOReturnSuccess) { DEBUG_ALWAYS("Unable to unserialize image, len=%#x\n", (unsigned int)length); } } return handler; exit: delete handler; return nullptr; } IONVRAMCHRPHandler::IONVRAMCHRPHandler(OSSharedPtr &varDict) : _commonPartitionSize(0x800), _varDict(varDict) { } IOReturn IONVRAMCHRPHandler::flush(const uuid_t guid, IONVRAMOperation op) { IOReturn ret = kIOReturnSuccess; bool flushSystem; bool flushCommon; flushSystem = getSystemPartitionActive() && (uuid_compare(guid, gAppleSystemVariableGuid) == 0); flushCommon = uuid_compare(guid, gAppleNVRAMGuid) == 0; DEBUG_INFO("flushSystem=%d, flushCommon=%d\n", flushSystem, flushCommon); if (flushSystem || flushCommon) { const OSSymbol *canonicalKey; OSSharedPtr dictCopy; OSSharedPtr iter; uuid_string_t uuidString; dictCopy = OSDictionary::withDictionary(_varDict.get()); iter = OSCollectionIterator::withCollection(dictCopy.get()); require_action(dictCopy && iter, exit, ret = kIOReturnNoMemory); while ((canonicalKey = OSDynamicCast(OSSymbol, iter->getNextObject()))) { const char *varName; uuid_t varGuid; bool clear; parseVariableName(canonicalKey->getCStringNoCopy(), &varGuid, &varName); uuid_unparse(varGuid, uuidString); clear = ((flushSystem && (uuid_compare(varGuid, gAppleSystemVariableGuid) == 0)) || (flushCommon && (uuid_compare(varGuid, gAppleSystemVariableGuid) != 0))) && verifyPermission(op, varGuid, varName, getSystemPartitionActive()); if (clear) { DEBUG_INFO("Clearing entry for %s:%s\n", uuidString, varName); setVariableInternal(varGuid, varName, nullptr); } else { DEBUG_INFO("Keeping entry for %s:%s\n", uuidString, varName); } } } exit: return ret; } IOReturn IONVRAMCHRPHandler::reloadInternal(void) { uint32_t controllerBank; uint32_t controllerGen; controllerBank = findCurrentBank(&controllerGen); if (_currentBank != controllerBank) { DEBUG_ERROR("_currentBank 0x%x != controllerBank 0x%x", _currentBank, controllerBank); } if (_generation != controllerGen) { DEBUG_ERROR("_generation 0x%x != controllerGen 0x%x", _generation, controllerGen); } _currentBank = controllerBank; _generation = controllerGen; return kIOReturnSuccess; } void IONVRAMCHRPHandler::reload(void) { _reload = true; DEBUG_INFO("reload marked\n"); } IOReturn IONVRAMCHRPHandler::unserializeImage(const uint8_t *image, IOByteCount length) { IOReturn ret = kIOReturnInvalid; uint32_t partitionOffset, partitionLength; uint32_t currentLength, currentOffset = 0; uint32_t hdr_adler, calculated_adler; _commonPartitionOffset = 0xFFFFFFFF; _systemPartitionOffset = 0xFFFFFFFF; _version = validateNVRAMVersion(image, _bankSize, &_generation); require(_version != kNVRAMVersionUnknown, exit); if (_nvramImage) { IOFreeData(_nvramImage, _bankSize); } _nvramImage = IONewData(uint8_t, length); _bankSize = (uint32_t)length; bcopy(image, _nvramImage, _bankSize); hdr_adler = nvram_get_adler(_nvramImage, _version); calculated_adler = adler32_with_version(_nvramImage, _bankSize, _version); if (hdr_adler != calculated_adler) { panic("header adler %#08X != calculated_adler %#08X\n", hdr_adler, calculated_adler); } // Look through the partitions to find the common and system partitions. while (currentOffset < _bankSize) { bool common_partition; bool system_partition; const chrp_nvram_header_t * header = (chrp_nvram_header_t *)(_nvramImage + currentOffset); const uint8_t common_v1_name[sizeof(header->name)] = {NVRAM_CHRP_PARTITION_NAME_COMMON_V1}; const uint8_t common_v2_name[sizeof(header->name)] = {NVRAM_CHRP_PARTITION_NAME_COMMON_V2}; const uint8_t system_v1_name[sizeof(header->name)] = {NVRAM_CHRP_PARTITION_NAME_SYSTEM_V1}; const uint8_t system_v2_name[sizeof(header->name)] = {NVRAM_CHRP_PARTITION_NAME_SYSTEM_V2}; currentLength = header->len * NVRAM_CHRP_LENGTH_BLOCK_SIZE; if (currentLength < sizeof(chrp_nvram_header_t)) { break; } partitionOffset = currentOffset + sizeof(chrp_nvram_header_t); partitionLength = currentLength - sizeof(chrp_nvram_header_t); if ((partitionOffset + partitionLength) > _bankSize) { break; } common_partition = (memcmp(header->name, common_v1_name, sizeof(header->name)) == 0) || (memcmp(header->name, common_v2_name, sizeof(header->name)) == 0); system_partition = (memcmp(header->name, system_v1_name, sizeof(header->name)) == 0) || (memcmp(header->name, system_v2_name, sizeof(header->name)) == 0); if (common_partition) { _commonPartitionOffset = partitionOffset; _commonPartitionSize = partitionLength; } else if (system_partition) { _systemPartitionOffset = partitionOffset; _systemPartitionSize = partitionLength; } currentOffset += currentLength; } ret = kIOReturnSuccess; exit: _varDict = OSDictionary::withCapacity(1); DEBUG_ALWAYS("NVRAM : commonPartitionOffset - %#x, commonPartitionSize - %#x, systemPartitionOffset - %#x, systemPartitionSize - %#x\n", _commonPartitionOffset, _commonPartitionSize, _systemPartitionOffset, _systemPartitionSize); return ret; } IOReturn IONVRAMCHRPHandler::unserializeVariables(void) { uint32_t cnt, cntStart; const uint8_t *propName, *propData; uint32_t propNameLength, propDataLength, regionIndex; OSSharedPtr propSymbol; OSSharedPtr propObject; NVRAMRegionInfo *currentRegion; NVRAMRegionInfo variableRegions[] = { { kIONVRAMPartitionCommon, _commonPartitionOffset, _commonPartitionSize}, { kIONVRAMPartitionSystem, _systemPartitionOffset, _systemPartitionSize} }; DEBUG_INFO("...\n"); for (regionIndex = 0; regionIndex < ARRAY_SIZE(variableRegions); regionIndex++) { currentRegion = &variableRegions[regionIndex]; const uint8_t * imageData = _nvramImage + currentRegion->offset; if (currentRegion->size == 0) { continue; } DEBUG_INFO("region = %d\n", currentRegion->type); cnt = 0; while (cnt < currentRegion->size) { cntStart = cnt; // Break if there is no name. if (imageData[cnt] == '\0') { break; } // Find the length of the name. propName = imageData + cnt; for (propNameLength = 0; (cnt + propNameLength) < currentRegion->size; propNameLength++) { if (imageData[cnt + propNameLength] == '=') { break; } } // Break if the name goes past the end of the partition. if ((cnt + propNameLength) >= currentRegion->size) { break; } cnt += propNameLength + 1; propData = imageData + cnt; for (propDataLength = 0; (cnt + propDataLength) < currentRegion->size; propDataLength++) { if (imageData[cnt + propDataLength] == '\0') { break; } } // Break if the data goes past the end of the partition. if ((cnt + propDataLength) >= currentRegion->size) { break; } cnt += propDataLength + 1; if (convertPropToObject(propName, propNameLength, propData, propDataLength, propSymbol, propObject)) { OSSharedPtr canonicalKey; const char *varName = propSymbol.get()->getCStringNoCopy(); uint32_t variableLength = cnt - cntStart; DEBUG_INFO("adding %s, variableLength=%#x,dataLength=%#x\n", varName, variableLength, propDataLength); if (currentRegion->type == kIONVRAMPartitionCommon) { canonicalKey = keyWithGuidAndCString(gAppleNVRAMGuid, varName); } else if (currentRegion->type == kIONVRAMPartitionSystem) { canonicalKey = keyWithGuidAndCString(gAppleSystemVariableGuid, varName); } DEBUG_INFO("adding %s, dataLength=%u\n", varName, propDataLength); _varDict->setObject(canonicalKey.get(), propObject.get()); if (_provider->_diags) { _provider->_diags->logVariable(currentRegion->type, kIONVRAMOperationInit, varName, (void *)(uintptr_t)(cnt - cntStart)); } if (currentRegion->type == kIONVRAMPartitionSystem) { _systemUsed += variableLength; } else if (currentRegion->type == kIONVRAMPartitionCommon) { _commonUsed += variableLength; } } } } if (_provider->_diags) { OSSharedPtr val = OSNumber::withNumber(getSystemUsed(), 32); _provider->_diags->setProperty(kNVRAMSystemUsedKey, val.get()); DEBUG_INFO("%s=%u\n", kNVRAMSystemUsedKey, getSystemUsed()); val = OSNumber::withNumber(getCommonUsed(), 32); _provider->_diags->setProperty(kNVRAMCommonUsedKey, val.get()); DEBUG_INFO("%s=%u\n", kNVRAMCommonUsedKey, getCommonUsed()); } // Create the boot-args property if it is not in the dictionary. if (_provider->getProperty(kIONVRAMBootArgsKey) == nullptr) { propSymbol = OSSymbol::withCString(kIONVRAMBootArgsKey); propObject = OSString::withCStringNoCopy(""); _provider->setProperty(propSymbol.get(), propObject.get()); } _newData = true; DEBUG_INFO("%s _varDict=%p\n", __FUNCTION__, _varDict ? _varDict.get() : nullptr); return kIOReturnSuccess; } IOReturn IONVRAMCHRPHandler::serializeVariables(void) { IOReturn ret; bool ok = false; uint32_t length, maxLength, regionIndex; uint8_t *buffer, *tmpBuffer; const OSSymbol *tmpSymbol; OSObject *tmpObject; OSSharedPtr iter; OSSharedPtr generation; uint8_t *nvramImage; NVRAMRegionInfo *currentRegion; NVRAMRegionInfo variableRegions[] = { { kIONVRAMPartitionCommon, _commonPartitionOffset, _commonPartitionSize}, { kIONVRAMPartitionSystem, _systemPartitionOffset, _systemPartitionSize} }; require_action(_nvramController != nullptr, exit, (ret = kIOReturnNotReady, DEBUG_ERROR("No _nvramController\n"))); require_action(_newData == true, exit, (ret = kIOReturnSuccess, DEBUG_INFO("No _newData to sync\n"))); require_action(_bankSize != 0, exit, (ret = kIOReturnSuccess, DEBUG_INFO("No nvram size info\n"))); require_action(_nvramImage != nullptr, exit, (ret = kIOReturnSuccess, DEBUG_INFO("No nvram image info\n"))); nvramImage = IONewZeroData(uint8_t, _bankSize); require_action(nvramImage != nullptr, exit, (ret = kIOReturnNoMemory, DEBUG_ERROR("Can't create NVRAM image copy\n"))); DEBUG_INFO("...\n"); bcopy(_nvramImage, nvramImage, _bankSize); for (regionIndex = 0; regionIndex < ARRAY_SIZE(variableRegions); regionIndex++) { currentRegion = &variableRegions[regionIndex]; if (currentRegion->size == 0) { continue; } DEBUG_INFO("region = %d\n", currentRegion->type); buffer = tmpBuffer = nvramImage + currentRegion->offset; bzero(buffer, currentRegion->size); ok = true; maxLength = currentRegion->size; iter = OSCollectionIterator::withCollection(_varDict.get()); if (iter == nullptr) { ok = false; } while (ok) { uuid_t entryGuid; const char *entryName; tmpSymbol = OSDynamicCast(OSSymbol, iter->getNextObject()); if (tmpSymbol == nullptr) { break; } DEBUG_INFO("_varDict entry %s\n", tmpSymbol->getCStringNoCopy()); parseVariableName(tmpSymbol, &entryGuid, &entryName); if (getSystemPartitionActive()) { if (currentRegion->type == kIONVRAMPartitionSystem) { if (uuid_compare(entryGuid, gAppleSystemVariableGuid) != 0) { DEBUG_INFO("Skipping %s because not system var\n", entryName); continue; } } else if (currentRegion->type == kIONVRAMPartitionCommon) { if (uuid_compare(entryGuid, gAppleSystemVariableGuid) == 0) { DEBUG_INFO("Skipping %s for common region\n", entryName); continue; } } } DEBUG_INFO("adding variable %s\n", entryName); tmpObject = _varDict->getObject(tmpSymbol); length = maxLength; ok = convertObjectToProp(tmpBuffer, &length, entryName, tmpObject); if (ok) { tmpBuffer += length; maxLength -= length; } } if (!ok) { ret = kIOReturnNoSpace; IODeleteData(nvramImage, uint8_t, _bankSize); break; } if (currentRegion->type == kIONVRAMPartitionSystem) { _systemUsed = (uint32_t)(tmpBuffer - buffer); } else if (currentRegion->type == kIONVRAMPartitionCommon) { _commonUsed = (uint32_t)(tmpBuffer - buffer); } } DEBUG_INFO("ok=%d\n", ok); require(ok, exit); nvram_set_apple_header(nvramImage, _bankSize, ++_generation, _version); _currentBank = (_currentBank + 1) % _bankCount; ret = _nvramController->select(_currentBank); DEBUG_IFERROR(ret, "_currentBank=%#x, select=%#x\n", _currentBank, ret); ret = _nvramController->eraseBank(); DEBUG_IFERROR(ret, "eraseBank=%#x\n", ret); ret = _nvramController->write(0, nvramImage, _bankSize); DEBUG_IFERROR(ret, "write=%#x\n", ret); _nvramController->sync(); if (_nvramImage) { IODeleteData(_nvramImage, uint8_t, _bankSize); } _nvramImage = nvramImage; _newData = false; exit: return ret; } IOReturn IONVRAMCHRPHandler::setVariableInternal(const uuid_t varGuid, const char *variableName, OSObject *object) { uint32_t newSize = 0; uint32_t existingSize = 0; bool remove = (object == nullptr); OSObject *existing; OSSharedPtr canonicalKey; bool systemVar; systemVar = (uuid_compare(varGuid, gAppleSystemVariableGuid) == 0); canonicalKey = keyWithGuidAndCString(varGuid, variableName); if ((existing = _varDict->getObject(canonicalKey.get()))) { convertObjectToProp(nullptr, &existingSize, variableName, existing); } if (remove == false) { convertObjectToProp(nullptr, &newSize, variableName, object); DEBUG_INFO("setting %s, systemVar=%d, existingSize=%u, newSize=%u\n", canonicalKey.get()->getCStringNoCopy(), systemVar, existingSize, newSize); if (systemVar) { if ((newSize + _systemUsed - existingSize) > _systemPartitionSize) { DEBUG_ERROR("No space left in system partition, need=%#x, _systemUsed=%#x, _systemPartitionSize=%#x\n", newSize, _systemUsed, _systemPartitionSize); return kIOReturnNoSpace; } else { _systemUsed = _systemUsed + newSize - existingSize; } } else { if ((newSize + _commonUsed - existingSize) > _commonPartitionSize) { DEBUG_ERROR("No space left in common partition, need=%#x, _commonUsed=%#x, _commonPartitionSize=%#x\n", newSize, _commonUsed, _commonPartitionSize); return kIOReturnNoSpace; } else { _commonUsed = _commonUsed + newSize - existingSize; } } _varDict->setObject(canonicalKey.get(), object); if (_provider->_diags) { _provider->_diags->logVariable(getPartitionTypeForGUID(varGuid), kIONVRAMOperationWrite, variableName, (void *)(uintptr_t)newSize); } } else { DEBUG_INFO("removing %s, systemVar=%d, existingSize=%u\n", canonicalKey.get()->getCStringNoCopy(), systemVar, existingSize); if (systemVar) { _systemUsed -= existingSize; } else { _commonUsed -= existingSize; } _varDict->removeObject(canonicalKey.get()); if (_provider->_diags) { _provider->_diags->logVariable(getPartitionTypeForGUID(varGuid), kIONVRAMOperationDelete, variableName, nullptr); } } if (_provider->_diags) { OSSharedPtr val = OSNumber::withNumber(getSystemUsed(), 32); _provider->_diags->setProperty(kNVRAMSystemUsedKey, val.get()); DEBUG_INFO("%s=%u\n", kNVRAMSystemUsedKey, getSystemUsed()); val = OSNumber::withNumber(getCommonUsed(), 32); _provider->_diags->setProperty(kNVRAMCommonUsedKey, val.get()); DEBUG_INFO("%s=%u\n", kNVRAMCommonUsedKey, getCommonUsed()); } _newData = true; return kIOReturnSuccess; } IOReturn IONVRAMCHRPHandler::setVariable(const uuid_t varGuid, const char *variableName, OSObject *object) { uuid_t destGuid; if (getSystemPartitionActive()) { // System region case, if they're using the GUID directly or it's on the system allow list // force it to use the System GUID if ((uuid_compare(varGuid, gAppleSystemVariableGuid) == 0) || variableInAllowList(variableName)) { uuid_copy(destGuid, gAppleSystemVariableGuid); } else { uuid_copy(destGuid, varGuid); } } else { // No system region, store System GUID as Common GUID if ((uuid_compare(varGuid, gAppleSystemVariableGuid) == 0) || variableInAllowList(variableName)) { uuid_copy(destGuid, gAppleNVRAMGuid); } else { uuid_copy(destGuid, varGuid); } } return setVariableInternal(destGuid, variableName, object); } uint32_t IONVRAMCHRPHandler::findCurrentBank(uint32_t *gen) { struct apple_nvram_header storeHeader; uint32_t maxGen = 0; uint32_t currentBank = 0; for (unsigned int i = 0; i < _bankCount; i++) { NVRAMVersion bankVer; uint32_t bankGen = 0; _nvramController->select(i); _nvramController->read(0, (uint8_t *)&storeHeader, sizeof(storeHeader)); bankVer = validateNVRAMVersion((uint8_t *)&storeHeader, sizeof(storeHeader), &bankGen); if ((bankVer != kNVRAMVersionUnknown) && (bankGen >= maxGen)) { currentBank = i; maxGen = bankGen; } } DEBUG_ALWAYS("currentBank=%#x, gen=%#x", currentBank, maxGen); *gen = maxGen; return currentBank; } bool IONVRAMCHRPHandler::setController(IONVRAMController *controller) { IOReturn ret; if (_nvramController == NULL) { _nvramController = controller; } DEBUG_INFO("Controller name: %s\n", _nvramController->getName()); ret = reloadInternal(); if (ret != kIOReturnSuccess) { DEBUG_ERROR("reloadInternal failed, ret=0x%08x\n", ret); } return true; } IOReturn IONVRAMCHRPHandler::sync(void) { IOReturn ret; if (_reload) { ret = reloadInternal(); require_noerr_action(ret, exit, DEBUG_ERROR("Reload failed, ret=%#x", ret)); _reload = false; } ret = serializeVariables(); require_noerr_action(ret, exit, DEBUG_ERROR("serializeVariables failed, ret=%#x", ret)); exit: return ret; } uint32_t IONVRAMCHRPHandler::getGeneration(void) const { return _generation; } uint32_t IONVRAMCHRPHandler::getVersion(void) const { return _version; } uint32_t IONVRAMCHRPHandler::getSystemUsed(void) const { return _systemUsed; } uint32_t IONVRAMCHRPHandler::getCommonUsed(void) const { return _commonUsed; } bool IONVRAMCHRPHandler::getSystemPartitionActive(void) const { return _systemPartitionSize != 0; } OSSharedPtr IONVRAMCHRPHandler::unescapeBytesToData(const uint8_t *bytes, uint32_t length) { OSSharedPtr data; uint32_t totalLength = 0; uint32_t offset, offset2; uint8_t byte; bool ok; // Calculate the actual length of the data. ok = true; totalLength = 0; for (offset = 0; offset < length;) { byte = bytes[offset++]; if (byte == 0xFF) { byte = bytes[offset++]; if (byte == 0x00) { ok = false; break; } offset2 = byte & 0x7F; } else { offset2 = 1; } totalLength += offset2; } if (ok) { // Create an empty OSData of the correct size. data = OSData::withCapacity(totalLength); if (data != nullptr) { for (offset = 0; offset < length;) { byte = bytes[offset++]; if (byte == 0xFF) { byte = bytes[offset++]; offset2 = byte & 0x7F; byte = (byte & 0x80) ? 0xFF : 0x00; } else { offset2 = 1; } data->appendByte(byte, offset2); } } } return data; } OSSharedPtr IONVRAMCHRPHandler::escapeDataToData(OSData * value) { OSSharedPtr result; OSBoundedPtr startPtr; const uint8_t *endPtr; const uint8_t *valueBytesPtr; OSBoundedPtr wherePtr; uint8_t byte; bool ok = true; valueBytesPtr = (const uint8_t *) value->getBytesNoCopy(); endPtr = valueBytesPtr + value->getLength(); wherePtr = OSBoundedPtr(valueBytesPtr, valueBytesPtr, endPtr); result = OSData::withCapacity((unsigned int)(endPtr - wherePtr)); if (!result) { return result; } while (wherePtr < endPtr) { startPtr = wherePtr; byte = *wherePtr++; if ((byte == 0x00) || (byte == 0xFF)) { for (; ((wherePtr - startPtr) < 0x7F) && (wherePtr < endPtr) && (byte == *wherePtr); wherePtr++) { } ok &= result->appendByte(0xff, 1); byte = (byte & 0x80) | ((uint8_t)(wherePtr - startPtr)); } ok &= result->appendByte(byte, 1); } ok &= result->appendByte(0, 1); if (!ok) { result.reset(); } return result; } bool IONVRAMCHRPHandler::convertPropToObject(const uint8_t *propName, uint32_t propNameLength, const uint8_t *propData, uint32_t propDataLength, const OSSymbol **propSymbol, OSObject **propObject) { OSSharedPtr delimitedName; OSSharedPtr tmpSymbol; OSSharedPtr tmpNumber; OSSharedPtr tmpString; OSSharedPtr tmpObject = nullptr; delimitedName = OSString::withCString((const char *)propName, propNameLength); tmpSymbol = OSSymbol::withString(delimitedName.get()); if (tmpSymbol == nullptr) { return false; } switch (getVariableType(tmpSymbol.get())) { case kOFVariableTypeBoolean: if (!strncmp("true", (const char *)propData, propDataLength)) { tmpObject.reset(kOSBooleanTrue, OSRetain); } else if (!strncmp("false", (const char *)propData, propDataLength)) { tmpObject.reset(kOSBooleanFalse, OSRetain); } break; case kOFVariableTypeNumber: tmpNumber = OSNumber::withNumber(strtol((const char *)propData, nullptr, 0), 32); if (tmpNumber != nullptr) { tmpObject = tmpNumber; } break; case kOFVariableTypeString: tmpString = OSString::withCString((const char *)propData, propDataLength); if (tmpString != nullptr) { tmpObject = tmpString; } break; case kOFVariableTypeData: tmpObject = unescapeBytesToData(propData, propDataLength); break; default: break; } if (tmpObject == nullptr) { tmpSymbol.reset(); return false; } *propSymbol = tmpSymbol.detach(); *propObject = tmpObject.detach(); return true; } bool IONVRAMCHRPHandler::convertPropToObject(const uint8_t *propName, uint32_t propNameLength, const uint8_t *propData, uint32_t propDataLength, OSSharedPtr& propSymbol, OSSharedPtr& propObject) { const OSSymbol* propSymbolRaw = nullptr; OSObject* propObjectRaw = nullptr; bool ok = convertPropToObject(propName, propNameLength, propData, propDataLength, &propSymbolRaw, &propObjectRaw); propSymbol.reset(propSymbolRaw, OSNoRetain); propObject.reset(propObjectRaw, OSNoRetain); return ok; } bool IONVRAMCHRPHandler::convertObjectToProp(uint8_t *buffer, uint32_t *length, const OSSymbol *propSymbol, OSObject *propObject) { return convertObjectToProp(buffer, length, propSymbol->getCStringNoCopy(), propObject); } bool IONVRAMCHRPHandler::convertObjectToProp(uint8_t *buffer, uint32_t *length, const char *propName, OSObject *propObject) { uint32_t propNameLength, propDataLength, remaining, offset; IONVRAMVariableType propType; OSBoolean *tmpBoolean = nullptr; OSNumber *tmpNumber = nullptr; OSString *tmpString = nullptr; OSSharedPtr tmpData; propNameLength = (uint32_t)strlen(propName); propType = getVariableType(propName); offset = 0; remaining = 0; // Get the size of the data. propDataLength = 0xFFFFFFFF; switch (propType) { case kOFVariableTypeBoolean: tmpBoolean = OSDynamicCast(OSBoolean, propObject); if (tmpBoolean != nullptr) { propDataLength = 5; } break; case kOFVariableTypeNumber: tmpNumber = OSDynamicCast(OSNumber, propObject); if (tmpNumber != nullptr) { propDataLength = 10; } break; case kOFVariableTypeString: tmpString = OSDynamicCast(OSString, propObject); if (tmpString != nullptr) { propDataLength = tmpString->getLength(); } break; case kOFVariableTypeData: tmpData.reset(OSDynamicCast(OSData, propObject), OSNoRetain); if (tmpData != nullptr) { tmpData = escapeDataToData(tmpData.detach()); // escapeDataToData() adds the NULL byte to the data // subtract 1 here to keep offset consistent with the other cases propDataLength = tmpData->getLength() - 1; } break; default: break; } // Make sure the propertySize is known and will fit. if (propDataLength == 0xFFFFFFFF) { return false; } if (buffer) { // name + '=' + data + '\0' if ((propNameLength + propDataLength + 2) > *length) { return false; } remaining = *length; } *length = 0; // Copy the property name equal sign. offset += snprintf((char *)buffer, remaining, "%s=", propName); if (buffer) { if (remaining > offset) { buffer += offset; remaining = remaining - offset; } else { return false; } } switch (propType) { case kOFVariableTypeBoolean: if (tmpBoolean->getValue()) { offset += strlcpy((char *)buffer, "true", remaining); } else { offset += strlcpy((char *)buffer, "false", remaining); } break; case kOFVariableTypeNumber: { uint32_t tmpValue = tmpNumber->unsigned32BitValue(); if (tmpValue == 0xFFFFFFFF) { offset += strlcpy((char *)buffer, "-1", remaining); } else if (tmpValue < 1000) { offset += snprintf((char *)buffer, remaining, "%d", (uint32_t)tmpValue); } else { offset += snprintf((char *)buffer, remaining, "%#x", (uint32_t)tmpValue); } } break; case kOFVariableTypeString: offset += strlcpy((char *)buffer, tmpString->getCStringNoCopy(), remaining); break; case kOFVariableTypeData: if (buffer) { bcopy(tmpData->getBytesNoCopy(), buffer, propDataLength); } tmpData.reset(); offset += propDataLength; break; default: break; } *length = offset + 1; return true; }