-
Notifications
You must be signed in to change notification settings - Fork 562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
secboot: use half the mem for KDF in AddRecoveryKey #10619
Conversation
This commit renames total TotalSystemMemory to TotalUsableMemory and also changes the code to take the CmaTotal into account. This is the memory reserved by the "Contiguous Memory Allocator" and it is not usable for normal processes. This kind of memory is used e.g. by the framebuffer of the Raspberry Pi or by DSPs on certain boards.
Instead of benchmarking the KDF parameters for the recovery key (which takes some time to run) we can also use defaults for the KDF parameters. The defaults suggested by Chris are "4 iterations" and half the usable memory. This commit implements the suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, one suggestion about how to avoid the pointer usage just because we can avoid it and I think avoiding it makes the code more readable
@@ -57,7 +61,17 @@ func FormatEncryptedDevice(key EncryptionKey, label, node string) error { | |||
// volume created with FormatEncryptedDevice on the block device given by node. | |||
// The existing key to the encrypted volume is provided in the key argument. | |||
func AddRecoveryKey(key EncryptionKey, rkey RecoveryKey, node string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment on this function should probably explain the new defaults that are used here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a small comment, please let me know if this looks sufficient
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a bit hand wavy, but good enough 😄
switch { | ||
case strings.HasPrefix(l, "MemTotal:"): | ||
p = &memTotal | ||
case strings.HasPrefix(l, "CmaTotal:"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could do this without the pointer math with a helper function or even an anonymous lambda like this:
func TotalUsableMemory() (totalMem uint64, err error) {
f, err := os.Open(procMeminfo)
if err != nil {
return 0, err
}
defer f.Close()
s := bufio.NewScanner(f)
var memTotal, cmaTotal uint64
processLine := func(l string) (uint64, error) {
fields := strings.Fields(l)
if len(fields) != 3 || fields[2] != "kB" {
return 0, fmt.Errorf("cannot process unexpected meminfo entry %q", l)
}
v, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
return 0, fmt.Errorf("cannot convert memory size value: %v", err)
}
return v * 1024, nil
}
for s.Scan() {
l := strings.TrimSpace(s.Text())
var err error
switch {
case strings.HasPrefix(l, "MemTotal:"):
memTotal, err = processLine(l)
case strings.HasPrefix(l, "CmaTotal:"):
cmaTotal, err = processLine(l)
}
if err != nil {
return 0, err
}
}
if err := s.Err(); err != nil {
return 0, err
}
if memTotal == 0 {
return 0, fmt.Errorf("cannot determine the total amount of memory in the system from %s", procMeminfo)
}
return memTotal - cmaTotal, nil
}
if memTotal == 0 { | ||
return 0, fmt.Errorf("cannot determine the total amount of memory in the system from %s", procMeminfo) | ||
} | ||
return memTotal - cmaTotal, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to add a check to ensure that memTotal >= cmaTotal
, since the type of the value returned by this function is unsigned?
I doubt that memInfo would return such bogus values, but...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what happens with this computation on devices with something like android ION (see the PR making an interface for that), I suppose we will have to find out
This needs to be re-run with "run-nested" before it its merged. But let's wait for feedback from @chrisccoulson first. |
After discussing with Chris and Samuele we updated the KDF memory heuristic so that it takes more parameters in mind. It now considers the usable memory and substracts a hardcoded 384MB that is required to have a working system (a bit of a conservative estiamte) and then takes half of this for the KDF memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some nitpicks, mainly about the fact that there are an awful lot of manual numbers which can sometimes be confusing, if possible I would suggest using unit / types instead so it's more obvious what unit each number is for etc.
secboot/encrypt_sb.go
Outdated
kdfMem := (int(usableMem) - 384*1024*1024) / 2 | ||
// max 1 GB | ||
if kdfMem > 1024*1024*1024 { | ||
kdfMem = (1024 * 1024 * 1024) | ||
} | ||
if kdfMem < 32*1024 { | ||
kdfMem = 32 * 1024 | ||
} | ||
opts := &sb.KDFOptions{ | ||
MemoryKiB: kdfMem / 1024, | ||
ForceIterations: 4, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all of this would be more readable IMHO if we used the gadget.Size type here, but not necessary right now given the time pressure to try this out
// | ||
// CMA memory is taken up by e.g. the framebuffer on the Raspberry Pi or | ||
// by DSPs on specific boards. | ||
func TotalUsableMemory() (totalMem uint64, err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also again ideally this would return a quantity.Size instead of a uint64
I got confirmation that this approach helps and the whole boot time decreased from ~187s to ~165s during the install mode on a reference board we use. So this seems to be good to go. |
Thanks for the excellent comments. I'm inclined to start with this minimal version for 2.51 and then do a followup that moves to using the "quantity" pacakge. WDYT? Especially @pedronis and @chrisccoulson ? Your review would be really appreciated before we get this out. |
fine with me |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
two questions, one about the 384 reservation factor we can consider tweaking later on master.
// we want to use for the KDF. Doing it this way avoids the expensive | ||
// benchmark from cryptsetup. The recovery key is already 128bit | ||
// strong so we don't need to be super precise here. | ||
kdfMem := (int(usableMem) - 384*1024*1024) / 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we know how much memory we actually have free on the device during snap-bootstrap and during install mode? 384 is kind of for the full user space run mode? just wondering if it's a bit if it's too pessimistic for here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is probably on the high side of pessimism - I agree here. Unfortunately I don't have exact data.
I could maybe add one more estimation - there is MemToal and MemAvailable in /proc/meminfo, I could simply subtract those to get the memory needed. Given that at this point we are on an actual device that runs our base and kernel (in a tmpfs) it would probably a reasonable guess. Capped by [128Mb, 512Mb] maybe just to be on the safe side?
One reason I went a bit more pessimistic is that some architectures are less compact code-wise than others and may need more working memory (iirc that is the case for armhf vs arm64). And as this runs everywhere I wanted to be careful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as I said, I don't think it's a blocker but we might want to explore a bit more what to do on master later about this
secboot/encrypt_sb_test.go
Outdated
@@ -54,8 +57,8 @@ func (s *encryptSuite) TestFormatEncryptedDevice(c *C) { | |||
MetadataKiBSize: 2048, | |||
KeyslotsAreaKiBSize: 2560, | |||
KDFOptions: &sb.KDFOptions{ | |||
MemoryKiB: 32768, TargetDuration: 0, | |||
ForceIterations: 4, Parallel: 0, | |||
MemoryKiB: 32768, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
didn't we discuss that this also should become just 32?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, I was wondering and it seems it's best if I open a separate PR for this PR is about the recovery key and this change is about the main key. So unless you ffeel differently I do a small extra PR with the 32mb->32kb change for the main key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this now in #10645
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's fine, thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 taking into account the answers to the questions
Instead of benchmarking the KDF parameters for the recovery key
(which takes some time to run) we can also use defaults for the
KDF parameters. The defaults suggested by Chris are "4 iterations"
and half the usable memory. This commit implements the suggestions.