268 lines
7.8 KiB
Go
268 lines
7.8 KiB
Go
// Copyright 2017-2018 DigitalOcean.
|
|
//
|
|
// 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.
|
|
|
|
package smbios
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
)
|
|
|
|
// Anchor strings used to detect entry points.
|
|
var (
|
|
// Used when searching for an entry point in memory.
|
|
magicPrefix = []byte("_SM")
|
|
|
|
// Used to determine specific entry point types.
|
|
magic32 = []byte("_SM_")
|
|
magic64 = []byte("_SM3_")
|
|
magicDMI = []byte("_DMI_")
|
|
)
|
|
|
|
// An EntryPoint is an SMBIOS entry point. EntryPoints contain various
|
|
// properties about SMBIOS.
|
|
//
|
|
// Use a type assertion to access detailed EntryPoint information.
|
|
type EntryPoint interface {
|
|
// Table returns the memory address and maximum size of the SMBIOS table.
|
|
Table() (address, size int)
|
|
|
|
// Version returns the system's SMBIOS version.
|
|
Version() (major, minor, revision int)
|
|
}
|
|
|
|
// ParseEntryPoint parses an EntryPoint from the input stream.
|
|
func ParseEntryPoint(r io.Reader) (EntryPoint, error) {
|
|
// Prevent unbounded reads since this structure should be small.
|
|
b, err := ioutil.ReadAll(io.LimitReader(r, 64))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if l := len(b); l < 4 {
|
|
return nil, fmt.Errorf("too few bytes for SMBIOS entry point magic: %d", l)
|
|
}
|
|
|
|
switch {
|
|
case bytes.HasPrefix(b, magic32):
|
|
return parse32(b)
|
|
case bytes.HasPrefix(b, magic64):
|
|
return parse64(b)
|
|
}
|
|
|
|
return nil, fmt.Errorf("unrecognized SMBIOS entry point magic: %v", b[0:4])
|
|
}
|
|
|
|
var _ EntryPoint = &EntryPoint32Bit{}
|
|
|
|
// EntryPoint32Bit is the SMBIOS 32-bit Entry Point structure, used starting
|
|
// in SMBIOS 2.1.
|
|
type EntryPoint32Bit struct {
|
|
Anchor string
|
|
Checksum uint8
|
|
Length uint8
|
|
Major uint8
|
|
Minor uint8
|
|
MaxStructureSize uint16
|
|
EntryPointRevision uint8
|
|
FormattedArea [5]byte
|
|
IntermediateAnchor string
|
|
IntermediateChecksum uint8
|
|
StructureTableLength uint16
|
|
StructureTableAddress uint32
|
|
NumberStructures uint16
|
|
BCDRevision uint8
|
|
}
|
|
|
|
// Table implements EntryPoint.
|
|
func (e *EntryPoint32Bit) Table() (address, size int) {
|
|
return int(e.StructureTableAddress), int(e.StructureTableLength)
|
|
}
|
|
|
|
// Version implements EntryPoint.
|
|
func (e *EntryPoint32Bit) Version() (major, minor, revision int) {
|
|
return int(e.Major), int(e.Minor), 0
|
|
}
|
|
|
|
// parse32 parses an EntryPoint32Bit from b.
|
|
func parse32(b []byte) (*EntryPoint32Bit, error) {
|
|
l := len(b)
|
|
|
|
// Correct minimum length as of SMBIOS 3.1.1.
|
|
const expLen = 31
|
|
if l < expLen {
|
|
return nil, fmt.Errorf("expected SMBIOS 32-bit entry point minimum length of at least %d, but got: %d", expLen, l)
|
|
}
|
|
|
|
// Allow more data in the buffer than the actual length, for when the
|
|
// entry point is being read from system memory.
|
|
length := b[5]
|
|
if l < int(length) {
|
|
return nil, fmt.Errorf("expected SMBIOS 32-bit entry point actual length of at least %d, but got: %d", length, l)
|
|
}
|
|
|
|
// Look for intermediate anchor with DMI magic.
|
|
iAnchor := b[16:21]
|
|
if !bytes.Equal(iAnchor, magicDMI) {
|
|
return nil, fmt.Errorf("incorrect DMI magic in SMBIOS 32-bit entry point: %v", iAnchor)
|
|
}
|
|
|
|
// Entry point checksum occurs at index 4, compute and verify it.
|
|
const epChkIndex = 4
|
|
epChk := b[epChkIndex]
|
|
if err := checksum(epChk, epChkIndex, b[:length]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Since we already computed the checksum for the outer entry point,
|
|
// no real need to compute it for the intermediate entry point.
|
|
|
|
ep := &EntryPoint32Bit{
|
|
Anchor: string(b[0:4]),
|
|
Checksum: epChk,
|
|
Length: length,
|
|
Major: b[6],
|
|
Minor: b[7],
|
|
MaxStructureSize: binary.LittleEndian.Uint16(b[8:10]),
|
|
EntryPointRevision: b[10],
|
|
IntermediateAnchor: string(iAnchor),
|
|
IntermediateChecksum: b[21],
|
|
StructureTableLength: binary.LittleEndian.Uint16(b[22:24]),
|
|
StructureTableAddress: binary.LittleEndian.Uint32(b[24:28]),
|
|
NumberStructures: binary.LittleEndian.Uint16(b[28:30]),
|
|
BCDRevision: b[30],
|
|
}
|
|
copy(ep.FormattedArea[:], b[10:15])
|
|
|
|
return ep, nil
|
|
}
|
|
|
|
var _ EntryPoint = &EntryPoint64Bit{}
|
|
|
|
// EntryPoint64Bit is the SMBIOS 64-bit Entry Point structure, used starting
|
|
// in SMBIOS 3.0.
|
|
type EntryPoint64Bit struct {
|
|
Anchor string
|
|
Checksum uint8
|
|
Length uint8
|
|
Major uint8
|
|
Minor uint8
|
|
Revision uint8
|
|
EntryPointRevision uint8
|
|
Reserved uint8
|
|
StructureTableMaxSize uint32
|
|
StructureTableAddress uint64
|
|
}
|
|
|
|
// Table implements EntryPoint.
|
|
func (e *EntryPoint64Bit) Table() (address, size int) {
|
|
return int(e.StructureTableAddress), int(e.StructureTableMaxSize)
|
|
}
|
|
|
|
// Version implements EntryPoint.
|
|
func (e *EntryPoint64Bit) Version() (major, minor, revision int) {
|
|
return int(e.Major), int(e.Minor), int(e.Revision)
|
|
}
|
|
|
|
const (
|
|
// expLen64 is the expected minimum length of a 64-bit entry point.
|
|
// Correct minimum length as of SMBIOS 3.1.1.
|
|
expLen64 = 24
|
|
|
|
// chkIndex64 is the index of the checksum byte in a 64-bit entry point.
|
|
chkIndex64 = 5
|
|
)
|
|
|
|
// parse64 parses an EntryPoint64Bit from b.
|
|
func parse64(b []byte) (*EntryPoint64Bit, error) {
|
|
l := len(b)
|
|
|
|
// Ensure expected minimum length.
|
|
if l < expLen64 {
|
|
return nil, fmt.Errorf("expected SMBIOS 64-bit entry point minimum length of at least %d, but got: %d", expLen64, l)
|
|
}
|
|
|
|
// Allow more data in the buffer than the actual length, for when the
|
|
// entry point is being read from system memory.
|
|
length := b[6]
|
|
if l < int(length) {
|
|
return nil, fmt.Errorf("expected SMBIOS 64-bit entry point actual length of at least %d, but got: %d", length, l)
|
|
}
|
|
|
|
// Checksum occurs at index 5, compute and verify it.
|
|
chk := b[chkIndex64]
|
|
if err := checksum(chk, chkIndex64, b); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &EntryPoint64Bit{
|
|
Anchor: string(b[0:5]),
|
|
Checksum: chk,
|
|
Length: length,
|
|
Major: b[7],
|
|
Minor: b[8],
|
|
Revision: b[9],
|
|
EntryPointRevision: b[10],
|
|
Reserved: b[11],
|
|
StructureTableMaxSize: binary.LittleEndian.Uint32(b[12:16]),
|
|
StructureTableAddress: binary.LittleEndian.Uint64(b[16:24]),
|
|
}, nil
|
|
}
|
|
|
|
// checksum computes the checksum of b using the starting value of start, and
|
|
// skipping the checksum byte which occurs at index chkIndex.
|
|
//
|
|
// checksum assumes that b has already had its bounds checked.
|
|
func checksum(start uint8, chkIndex int, b []byte) error {
|
|
chk := start
|
|
for i := range b {
|
|
// Checksum computation does not include index of checksum byte.
|
|
if i == chkIndex {
|
|
continue
|
|
}
|
|
|
|
chk += b[i]
|
|
}
|
|
|
|
if chk != 0 {
|
|
return fmt.Errorf("invalid entry point checksum %#02x from initial checksum %#02x", chk, start)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WindowsEntryPoint contains SMBIOS Table entry point data returned from
|
|
// GetSystemFirmwareTable. As raw access to the underlying memory is not given,
|
|
// the full breadth of information is not available.
|
|
type WindowsEntryPoint struct {
|
|
Size uint32
|
|
MajorVersion byte
|
|
MinorVersion byte
|
|
Revision byte
|
|
}
|
|
|
|
// Table implements EntryPoint. The returned address will always be 0, as it
|
|
// is not returned by GetSystemFirmwareTable.
|
|
func (e *WindowsEntryPoint) Table() (address, size int) {
|
|
return 0, int(e.Size)
|
|
}
|
|
|
|
// Version implements EntryPoint.
|
|
func (e *WindowsEntryPoint) Version() (major, minor, revision int) {
|
|
return int(e.MajorVersion), int(e.MinorVersion), int(e.Revision)
|
|
}
|