Does this mean that changing one variable might affect other variables stored in the same 4kb block.
I am of course concluding that if the max variable size is 2kb, there can be more than one variable stored in a 4kb block ... or is that a wrong conclusion to start with?
"Changing" a variable actually means deleting it and creating a new one. Thus, if you execute
nvram MyVariable=Value
, then you execute nvram MyVariable=Vague
, the instance of MyVariable
is first marked as deleted (one bit in the variable's header is changed from 1->0, so the entire block does not need to be erased), then a new entry is created for MyVariable
, with a value of Vague
.By continually deleting/adding, they decrease the number of block-erase operations required; they only do block erases when the entire VSS approaches its capacity (when the free space falls below 2048 bytes), or when the user manually requests an NVRAM wipe. In both cases, they block-erase the entire VSS.
Variables are stored contiguously, so yes, there are multiple variables per 4k block. The hypothetical limit would be ~110 variables in a single block, assuming 1-character names and 1-byte values.
If I may include a related issue from another thread:
@Bmju ... do you happen to know what will happen if a process tries to write an NVRAM variable that is larger than the max allowed? The max on Legacy MacPro is apparently 2kb based on findings by @Syncretic . What will happen if something tries to write a 3kb or 4kb sized variable for instance?
If it will simply get discarded without any negative impact, then the UEFI Windows 64kb size might not be a problem at the end of the day. If it is the opposite, I suppose OpenCore could be updated to also filter such out (filter on size) and return a security violation error similar to the Windows Certificates. I just added such a filter to RefindPlus (locally, not yet pushed) but wondering whether it is worth it.
I think that maybe this don't apply to Macs, or at least to all Macs.
One anedoctal example, yesterday I had a crash with myMacPro9,2MacBookPro9,2 and the aapl,panic-info was over 5KB. Since SIP was enabled I couldn't dump the BootROM, but I saved the nvram -xp to a file. If my back of the napkin math is correct, 5KB base64 encoded = ~3.5KB.
I'm gonna do some investigation for @Bmju to see the needed GUIDs and I'll also check for the maximum entry size I can find.
Edited to correct MacPro9,2 to MacBookPro9,2
I have not really analyzed any BootROMs other than the MP5,1 144.0.0.0.0, but I have done some cursory looking at a few others. It appears that different Macs (e.g. iMac, MacBook Pro) use different variations of Apple's EFI implementation, so it's entirely plausible that a MBP9.2 could allow variables larger than 2048 bytes. The only assertion I can make with any certainty is that the MP5,1 144.0.0.0.0 EFI should always limit variables to 2048 bytes (or, possibly, behave oddly if a larger variable is somehow introduced during PEI/DXE).
The
gRT->SetVariable()
code should return EFI_INVALID_PARAMETER if an incoming variable would consume more than 2048 bytes (that total includes the 32-byte variable header, the NULL-terminated UTF-16 variable name, and the binary variable value, so the value must by definition be no more than 2012 bytes (given a 1-character variable name)). It appears that post-BootServices (while MacOS is running), the only exposed variable manipulation is via gRT->SetVariable()
, so the 2048-byte limit should be enforced for variables coming from MacOS (unless the MacOS NVRAM driver itself tries to discover the NVRAM mapping and directly manipulate the VSS - I haven't looked at the MacOS NVRAM code).I suppose where I am getting muddled up is that when writing back, changed variables apparently get appended to the end of the store and the existing record just gets invalidated.
So in a case with Var A, B & C of 1kb each in a block plus Var D of 2kb spanning into the next, does a change to Var A mean Vars B, C & D therefore have to go along for the ride to the end of the store ... with Var D taking those from the next block along and so on?
Obviously can't be the case but just trying to understand the likely mechanics.
Concrete (but bogus) example, using round sizes:
Code:
0x0000 VarA (valid)
0x0400 VarB (valid)
0x0800 VarC (valid)
0x0C00 VarD (valid)
0x1000 (Free Space begins)
Code:
0x0000 VarA (deleted) (meaning the "valid" bit was set to 0)
0x0400 VarB (valid)
0x0800 VarC (valid)
0x0C00 VarD (valid)
0x1000 VarA (valid) (new value)
0x1400 (Free Space begins)
When free space falls below the 2048-byte threshold (round sizes used and example truncated for simplicity):
Code:
0x0000 VarA (deleted)
0x0400 VarB (valid)
0x0800 VarC (deleted)
0x0C00 VarD (valid)
0x1000 VarA (valid)
0x1400 VarE (valid)
0x1800 VarF (deleted)
0x1900 VarC (valid)
0x1D00 VarF (valid)
0x1E00 (Free Space begins)
Code:
0x0000 VarB (valid)
0x0400 VarD (valid)
0x0800 VarA (valid)
0x0C00 VarE (valid)
0x1000 VarC (valid)
0x1400 VarF (valid)
0x1500 (Free Space begins)