Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/288.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
windows: Resolve kernel image path via %SystemRoot% so KernelVersion works on hosts whose system drive is not C:\.
```
47 changes: 45 additions & 2 deletions providers/windows/kernel_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,56 @@
package windows

import (
"os"
"path/filepath"

windows "github.com/elastic/go-windows"
"golang.org/x/sys/windows/registry"
)

const windowsKernelExe = `C:\Windows\System32\ntoskrnl.exe`
// fallbackSystemRoot is the last-resort default when the registry query and
// both environment variables are unavailable.
const fallbackSystemRoot = `C:\Windows`

// systemRootFromRegistry reads the SystemRoot value from
// HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion, which reflects the
// actual Windows directory regardless of the process environment. Returns ""
// on any error so the caller can fall back gracefully.
func systemRootFromRegistry() string {
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SOFTWARE\Microsoft\Windows NT\CurrentVersion`,
registry.READ|registry.WOW64_64KEY)
if err != nil {
return ""
}
defer k.Close()
val, _, err := k.GetStringValue("SystemRoot")
if err != nil {
return ""
}
return val
}

// kernelExePath returns the absolute path to the running kernel image.
// It prefers the registry (immune to a stripped process environment), then
// falls back to %SystemRoot% / %WINDIR%, then to the hardcoded default.
// See #287.
func kernelExePath() string {
root := systemRootFromRegistry()
if root == "" {
root = os.Getenv("SystemRoot")
}
if root == "" {
root = os.Getenv("WINDIR")
}
if root == "" {
root = fallbackSystemRoot
}
return filepath.Join(root, "System32", "ntoskrnl.exe")
}

func KernelVersion() (string, error) {
versionData, err := windows.GetFileVersionInfo(windowsKernelExe)
versionData, err := windows.GetFileVersionInfo(kernelExePath())
if err != nil {
return "", err
}
Expand Down
95 changes: 95 additions & 0 deletions providers/windows/kernel_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package windows

import (
"path/filepath"
"testing"
)

// resolveKernelExePath is like kernelExePath but accepts an injected
// systemRoot string so tests can exercise the env-var fallback chain
// without touching the registry.
func resolveKernelExePath(regRoot, systemRoot, windir string) string {
root := regRoot
if root == "" {
root = systemRoot
}
if root == "" {
root = windir
}
if root == "" {
root = fallbackSystemRoot
}
return filepath.Join(root, "System32", "ntoskrnl.exe")
}

func TestKernelExePath(t *testing.T) {
tests := []struct {
name string
regRoot string // simulated registry value (empty = registry miss)
envRoot string // %SystemRoot%
windir string // %WINDIR%
want string
}{
{
// Registry wins even when env vars differ -- the primary path.
name: "registry value used when present",
regRoot: `W:\Windows`,
envRoot: `C:\Windows`,
want: `W:\Windows\System32\ntoskrnl.exe`,
},
{
// Regression for #287: registry absent, non-default drive via env.
name: "SystemRoot env fallback",
envRoot: `W:\Windows`,
want: `W:\Windows\System32\ntoskrnl.exe`,
},
{
name: "WINDIR fallback when SystemRoot empty",
windir: `D:\WINNT`,
want: `D:\WINNT\System32\ntoskrnl.exe`,
},
{
name: "hardcoded fallback when all sources absent",
want: `C:\Windows\System32\ntoskrnl.exe`,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got := resolveKernelExePath(tc.regRoot, tc.envRoot, tc.windir)
if got != tc.want {
t.Fatalf("resolveKernelExePath() = %q, want %q", got, tc.want)
}
})
}
}

// TestKernelExePathLive checks that the live kernelExePath (registry + env)
// returns a non-empty path ending in ntoskrnl.exe.
func TestKernelExePathLive(t *testing.T) {
p := kernelExePath()
if p == "" {
t.Fatal("kernelExePath() returned empty string")
}
if filepath.Base(p) != "ntoskrnl.exe" {
t.Fatalf("kernelExePath() = %q, want path ending in ntoskrnl.exe", p)
}
}
Loading