Skip to content
Open
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
115 changes: 115 additions & 0 deletions kernel_tainted.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright The Prometheus Authors
// Licensed 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.

//go:build !windows

package procfs

import (
"bytes"
"fmt"
"io"
"strconv"
"strings"

"github.com/prometheus/procfs/internal/util"
)

// KernelTaintBit represents a single kernel taint flag.
type KernelTaintBit struct {
// Index is the zero-based bit position in the tainted bitmask.
Index int
// Flag is the letter code identifying this taint (e.g. "L" for soft lockup).
// See https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html
Flag string
// Description is a human-readable explanation of what this taint means.
Description string
// Set indicates whether this taint flag is currently active.
Set bool
}

// KernelTainted holds the kernel taint state read from /proc/sys/kernel/tainted.
type KernelTainted struct {
// Value is the raw integer from /proc/sys/kernel/tainted.
Value uint64
// Bits contains each known taint flag and whether it is currently set.
// Flags are listed in bit-position order (bit 0 first).
Bits []KernelTaintBit
}

// kernelTaintBitDefs lists all known kernel taint flags in bit-position order.
// Reference: https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html
var kernelTaintBitDefs = []struct {
flag string
description string
}{
{"P", "Proprietary module was loaded."},
{"F", "Module was force loaded."},
{"S", "SMP kernel oops on an officially SMP incapable processor."},
{"R", "Module was force unloaded."},
{"M", "Processor reported a Machine Check Exception (MCE)."},
{"B", "Bad page referenced or some unexpected page flags."},
{"U", "Taint requested by userspace application."},
{"D", "Kernel died recently, i.e. there was an OOPS or BUG."},
{"A", "An ACPI table was overridden by user."},
{"W", "Kernel issued warning."},
{"C", "Staging driver was loaded."},
{"I", "Workaround for bug in platform firmware applied."},
{"O", "Externally-built (out-of-tree) module was loaded."},
{"E", "Unsigned module was loaded."},
{"L", "Soft lockup occurred."},
{"K", "Kernel has been live patched."},
{"X", "Auxiliary taint, defined and used by distros."},
{"T", "The kernel was built with the struct randomization plugin."},
{"N", "An in-kernel test such as a KUnit test has been run."},
{"J", "Userspace used a mutating debug operation in fwctl."},
}

// KernelTainted reads /proc/sys/kernel/tainted and returns the raw taint value
// along with each known flag parsed into a KernelTainted struct.
// See https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html
func (fs FS) KernelTainted() (KernelTainted, error) {
data, err := util.ReadFileNoStat(fs.proc.Path("sys", "kernel", "tainted"))
if err != nil {
return KernelTainted{}, err
}
return parseTainted(bytes.NewReader(data))
}

// parseTainted parses the content of /proc/sys/kernel/tainted.
func parseTainted(r io.Reader) (KernelTainted, error) {
data, err := io.ReadAll(r)
if err != nil {
return KernelTainted{}, err
}

value, err := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
if err != nil {
return KernelTainted{}, fmt.Errorf("couldn't parse tainted value: %w", err)
}

bits := make([]KernelTaintBit, len(kernelTaintBitDefs))
for i, def := range kernelTaintBitDefs {
bits[i] = KernelTaintBit{
Index: i,
Flag: def.flag,
Description: def.description,
Set: value&(1<<uint(i)) != 0,
}
}

return KernelTainted{
Value: value,
Bits: bits,
}, nil
}
89 changes: 89 additions & 0 deletions kernel_tainted_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright The Prometheus Authors
// Licensed 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.

//go:build !windows

package procfs

import (
"strings"
"testing"
)

func TestKernelTainted(t *testing.T) {
fs, err := NewFS(procfsFixtures)
if err != nil {
t.Fatalf("failed to access %s: %v", procfsFixtures, err)
}

tainted, err := fs.KernelTainted()
if err != nil {
t.Fatalf("failed to collect %s/sys/kernel/tainted: %v", procfsFixtures, err)
}

// Fixture value is 12288 = bit 12 (O) + bit 13 (E).
if tainted.Value != 12288 {
t.Errorf("tainted value: want %d, got %d", 12288, tainted.Value)
}

const wantBits = 20
if len(tainted.Bits) != wantBits {
t.Fatalf("tainted bits length: want %d, got %d", wantBits, len(tainted.Bits))
}

for _, tc := range []struct {
index int
flag string
set bool
}{
{0, "P", false},
{12, "O", true}, // Externally-built (out-of-tree) module
{13, "E", true}, // Unsigned module
{14, "L", false}, // Soft lockup — must be clear
{17, "T", false},
{18, "N", false},
{19, "J", false},
} {
b := tainted.Bits[tc.index]
if b.Index != tc.index {
t.Errorf("bit[%d].Index: want %d, got %d", tc.index, tc.index, b.Index)
}
if b.Flag != tc.flag {
t.Errorf("bit[%d].Flag: want %q, got %q", tc.index, tc.flag, b.Flag)
}
if b.Set != tc.set {
t.Errorf("bit[%d] (%s): want Set=%v, got Set=%v", tc.index, tc.flag, tc.set, b.Set)
}
}
}

func TestParseTainted(t *testing.T) {
// Test parsing directly without filesystem access.
tainted, err := parseTainted(strings.NewReader("16384\n"))
if err != nil {
t.Fatalf("parseTainted failed: %v", err)
}

// 16384 = 2^14 → only bit 14 (L, soft lockup) should be set.
if tainted.Value != 16384 {
t.Errorf("value: want 16384, got %d", tainted.Value)
}
if !tainted.Bits[14].Set {
t.Errorf("bit 14 (L): want Set=true, got false")
}
for i, b := range tainted.Bits {
if i != 14 && b.Set {
t.Errorf("bit %d (%s): want Set=false, got true", i, b.Flag)
}
}
}
5 changes: 5 additions & 0 deletions testdata/fixtures.ttar
Original file line number Diff line number Diff line change
Expand Up @@ -3465,6 +3465,11 @@ Lines: 1
kill_process kill_thread trap errno trace log allow
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/proc/sys/kernel/tainted
Lines: 1
12288
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/proc/sys/vm
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Expand Down