Skip to content

Commit eaaaaee

Browse files
grematalexbrainman
authored andcommitted
windows/registry: correct KeyInfo.ModTime calculation
Filetime.Nanoseconds() has a major drawback: the returned int64 is too small to represent Filetime's zero value (January 1, 1601, [1]) in terms of nanoseconds since Epoch (00:00:00 UTC, January 1, 1970); MinInt64 [2] only dates back to year 1677. This has real-life implications, e.g., some Windows sub systems (Perflib, to name one) create registry keys with the last write time property set to zero (see note below). In this case, ModTime() reports an underflow-affected value of 2185-07-22T00:34:33.709551+01:00. This commit drops usage of Nanoseconds() in favor of a conversion that converts first to seconds and nanoseconds before gauging thus is capable to cover the full range of Filetime values. A note on last write time values: `lastWriteTime` is not exposed in the UI (say, `regedit`) or in PowerShell (`Get-ItemProperty`), you need to query `RegQueryInfoKeyA` [3] explicitly in some way [4] or another [5]. The source of the latter is offline by now but can be found elsewhere [6] and provides a quick way to show the value. [1] https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime [2] https://pkg.go.dev/math#pkg-constants [3] https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryinfokeya?redirectedfrom=MSDN [4] https://learn.microsoft.com/en-us/windows/win32/sysinfo/retrieving-the-last-write-time [5] https://learn-powershell.net/2014/12/18/retrieving-a-registry-key-lastwritetime-using-powershell/ [6] https://github.com/wxrdnx/GetRegistryKeyLastWriteTimeAndClassName Fixes golang/go#74335. Change-Id: I83dc1d6b5e0c581bfb53d58ee08a21ed9c166b0f GitHub-Last-Rev: 0073d1d GitHub-Pull-Request: #251 Reviewed-on: https://go-review.googlesource.com/c/sys/+/682816 Reviewed-by: Alex Brainman <alex.brainman@gmail.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: David Chase <drchase@google.com>
1 parent 942780b commit eaaaaee

3 files changed

Lines changed: 46 additions & 1 deletion

File tree

windows/registry/export_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ package registry
99
func (k Key) SetValue(name string, valtype uint32, data []byte) error {
1010
return k.setValue(name, valtype, data)
1111
}
12+
13+
func (ki *KeyInfo) ModTimeZero() bool {
14+
return ki.modTimeZero()
15+
}

windows/registry/key.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,20 @@ type KeyInfo struct {
198198

199199
// ModTime returns the key's last write time.
200200
func (ki *KeyInfo) ModTime() time.Time {
201-
return time.Unix(0, ki.lastWriteTime.Nanoseconds())
201+
lastHigh, lastLow := ki.lastWriteTime.HighDateTime, ki.lastWriteTime.LowDateTime
202+
// 100-nanosecond intervals since January 1, 1601
203+
hsec := uint64(lastHigh)<<32 + uint64(lastLow)
204+
// Convert _before_ gauging; the nanosecond difference between Epoch (00:00:00
205+
// UTC, January 1, 1970) and Filetime's zero offset (January 1, 1601) is out
206+
// of bounds for int64: -11644473600*1e7*1e2 < math.MinInt64
207+
sec := int64(hsec/1e7) - 11644473600
208+
nsec := int64(hsec%1e7) * 100
209+
return time.Unix(sec, nsec)
210+
}
211+
212+
// modTimeZero reports whether the key's last write time is zero.
213+
func (ki *KeyInfo) modTimeZero() bool {
214+
return ki.lastWriteTime.LowDateTime == 0 && ki.lastWriteTime.HighDateTime == 0
202215
}
203216

204217
// Stat retrieves information about the open key k.

windows/registry/registry_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package registry_test
99
import (
1010
"bytes"
1111
"crypto/rand"
12+
"errors"
1213
"os"
1314
"syscall"
1415
"testing"
@@ -674,3 +675,30 @@ func GetDynamicTimeZoneInformation(dtzi *DynamicTimezoneinformation) (rc uint32,
674675
}
675676
return
676677
}
678+
679+
func TestModTimeZeroValue(t *testing.T) {
680+
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009`, registry.READ)
681+
if err != nil {
682+
if errors.Is(err, syscall.ERROR_FILE_NOT_FOUND) {
683+
t.Skip("Perflib key not found; skipping")
684+
}
685+
t.Fatal(err)
686+
}
687+
defer k.Close()
688+
689+
// Modification time of Perflib keys is known to be set to
690+
// Filetime's zero value: get stats and check.
691+
stats, err := k.Stat()
692+
if err != nil {
693+
t.Fatal(err)
694+
}
695+
// First verify input is zero (assume ModTimeZero uses it directly).
696+
if !stats.ModTimeZero() {
697+
t.Error("Modification time of Perflib key should be zero")
698+
}
699+
// Then check ModTime directly thus conversion implicitly.
700+
modTime := stats.ModTime()
701+
if !modTime.Equal(time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC)) {
702+
t.Errorf("ModTime should be 1601-01-01, but is %v", modTime)
703+
}
704+
}

0 commit comments

Comments
 (0)