├── AUTHORS ├── scripts ├── licensecheck.sh └── license.txt ├── .travis.yml ├── smbios ├── doc.go ├── structure.go ├── fuzz.go ├── stream_others.go ├── stream_unix.go ├── stream_linux.go ├── stream_integration_test.go ├── stream_memory.go ├── decoder_test.go ├── stream_windows_test.go ├── stream_memory_test.go ├── stream_windows.go ├── decoder.go ├── entrypoint_test.go └── entrypoint.go ├── CONTRIBUTING.md ├── cmd ├── lssmbios │ └── main.go └── lsdimms │ └── main.go ├── README.md └── LICENSE.md /AUTHORS: -------------------------------------------------------------------------------- 1 | Maintainer 2 | ---------- 3 | DigitalOcean, Inc 4 | 5 | Original Authors 6 | ---------------- 7 | Matt Layher 8 | 9 | Contributors 10 | ------------ 11 | Christopher Dudley -------------------------------------------------------------------------------- /scripts/licensecheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Verify that the correct license block is present in all Go source 4 | # files. 5 | EXPECTED=$(cat ./scripts/license.txt) 6 | 7 | # Scan each Go source file for license. 8 | EXIT=0 9 | GOFILES=$(find . -name "*.go") 10 | 11 | for FILE in $GOFILES; do 12 | BLOCK=$(head -n 14 $FILE) 13 | 14 | if [ "$BLOCK" != "$EXPECTED" ]; then 15 | echo "file missing license: $FILE" 16 | EXIT=1 17 | fi 18 | done 19 | 20 | exit $EXIT 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.x 4 | os: 5 | - linux 6 | sudo: required 7 | before_install: 8 | - go get github.com/golang/lint/golint 9 | - go get honnef.co/go/tools/cmd/staticcheck 10 | - go get -d ./... 11 | script: 12 | - ./scripts/licensecheck.sh 13 | - go build -tags=gofuzz ./... 14 | - go vet ./... 15 | - staticcheck ./... 16 | - golint -set_exit_status 17 | - go test -v -race ./... 18 | - go test -c ./smbios 19 | - sudo ./smbios.test -test.v -------------------------------------------------------------------------------- /scripts/license.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | -------------------------------------------------------------------------------- /smbios/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package smbios provides detection and access to System Management BIOS (SMBIOS) 16 | // and Desktop Management Interface (DMI) data and structures. 17 | package smbios 18 | -------------------------------------------------------------------------------- /smbios/structure.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios 16 | 17 | // A Header is a Structure's header. 18 | type Header struct { 19 | Type uint8 20 | Length uint8 21 | Handle uint16 22 | } 23 | 24 | // A Structure is an SMBIOS structure. 25 | type Structure struct { 26 | Header Header 27 | Formatted []byte 28 | Strings []string 29 | } 30 | -------------------------------------------------------------------------------- /smbios/fuzz.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //+build gofuzz 16 | 17 | package smbios 18 | 19 | import ( 20 | "bytes" 21 | ) 22 | 23 | func Fuzz(data []byte) int { 24 | return fuzzDecoder(data) 25 | } 26 | 27 | func fuzzDecoder(data []byte) int { 28 | d := NewDecoder(bytes.NewReader(data)) 29 | 30 | if _, err := d.Decode(); err != nil { 31 | return 0 32 | } 33 | 34 | return 1 35 | } 36 | -------------------------------------------------------------------------------- /smbios/stream_others.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //+build !dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows 16 | 17 | package smbios 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "runtime" 23 | ) 24 | 25 | // stream is not implemented for unsupported platforms. 26 | func stream() (io.ReadCloser, EntryPoint, error) { 27 | return nil, nil, fmt.Errorf("opening SMBIOS stream not implemented on %q", runtime.GOOS) 28 | } 29 | -------------------------------------------------------------------------------- /smbios/stream_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //+build dragonfly freebsd netbsd openbsd solaris 16 | 17 | // Linux intentionally omitted because it has an alternative method that 18 | // is used before attempting /dev/mem access. See stream_linux.go. 19 | 20 | package smbios 21 | 22 | import ( 23 | "io" 24 | ) 25 | 26 | // stream opens the SMBIOS entry point and an SMBIOS structure stream. 27 | func stream() (io.ReadCloser, EntryPoint, error) { 28 | // Use the standard UNIX-like system method. 29 | return devMemStream() 30 | } 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | The `go-smbios` project makes use of the [GitHub Flow](https://guides.github.com/introduction/flow/) 5 | for contributions. 6 | 7 | If you'd like to contribute to the project, please 8 | [open an issue](https://github.com/digitalocean/go-smbios/issues/new) or find an 9 | [existing issue](https://github.com/digitalocean/go-smbios/issues) that you'd like 10 | to take on. This ensures that efforts are not duplicated, and that a new feature 11 | aligns with the focus of the rest of the repository. 12 | 13 | Once your suggestion has been submitted and discussed, please be sure that your 14 | code meets the following criteria: 15 | - code is completely `gofmt`'d 16 | - new features or codepaths have appropriate test coverage 17 | - `go test ./...` passes 18 | - `go vet ./...` passes 19 | - `staticcheck ./...` passes 20 | - `golint ./...` returns no warnings, including documentation comment warnings 21 | 22 | In addition, if this is your first time contributing to the `go-smbios` project, 23 | add your name and email address to the 24 | [AUTHORS](https://github.com/digitalocean/go-smbios/blob/master/AUTHORS) file 25 | under the "Contributors" section using the format: 26 | `First Last `. 27 | 28 | Finally, submit a pull request for review! -------------------------------------------------------------------------------- /cmd/lssmbios/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Command lssmbios accesses and displays SMBIOS data. 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "log" 21 | 22 | "github.com/digitalocean/go-smbios/smbios" 23 | ) 24 | 25 | func main() { 26 | // Find SMBIOS data in operating system-specific location. 27 | rc, ep, err := smbios.Stream() 28 | if err != nil { 29 | log.Fatalf("failed to open stream: %v", err) 30 | } 31 | // Be sure to close the stream! 32 | defer rc.Close() 33 | 34 | // Decode SMBIOS structures from the stream. 35 | d := smbios.NewDecoder(rc) 36 | ss, err := d.Decode() 37 | if err != nil { 38 | log.Fatalf("failed to decode structures: %v", err) 39 | } 40 | 41 | // Determine SMBIOS version and table location from entry point. 42 | major, minor, rev := ep.Version() 43 | addr, size := ep.Table() 44 | 45 | fmt.Printf("SMBIOS %d.%d.%d - table: address: %#x, size: %d\n", 46 | major, minor, rev, addr, size) 47 | 48 | for _, s := range ss { 49 | fmt.Println(s) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /smbios/stream_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //+build linux 16 | 17 | package smbios 18 | 19 | import ( 20 | "io" 21 | "os" 22 | ) 23 | 24 | const ( 25 | // sysfs locations for SMBIOS information. 26 | sysfsDMI = "/sys/firmware/dmi/tables/DMI" 27 | sysfsEntryPoint = "/sys/firmware/dmi/tables/smbios_entry_point" 28 | ) 29 | 30 | // stream opens the SMBIOS entry point and an SMBIOS structure stream. 31 | func stream() (io.ReadCloser, EntryPoint, error) { 32 | // First, check for the sysfs location present in modern kernels. 33 | _, err := os.Stat(sysfsEntryPoint) 34 | switch { 35 | case err == nil: 36 | return sysfsStream(sysfsEntryPoint, sysfsDMI) 37 | case os.IsNotExist(err): 38 | // Fall back to the standard UNIX-like system method. 39 | return devMemStream() 40 | default: 41 | return nil, nil, err 42 | } 43 | } 44 | 45 | // sysfsStream reads the SMBIOS entry point and structure stream from 46 | // two files; usually the modern sysfs locations. 47 | func sysfsStream(entryPoint, dmi string) (io.ReadCloser, EntryPoint, error) { 48 | epf, err := os.Open(entryPoint) 49 | if err != nil { 50 | return nil, nil, err 51 | } 52 | defer epf.Close() 53 | 54 | ep, err := ParseEntryPoint(epf) 55 | if err != nil { 56 | return nil, nil, err 57 | } 58 | 59 | sf, err := os.Open(dmi) 60 | if err != nil { 61 | return nil, nil, err 62 | } 63 | 64 | return sf, ep, nil 65 | } 66 | -------------------------------------------------------------------------------- /smbios/stream_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios_test 16 | 17 | import ( 18 | "os" 19 | "runtime" 20 | "testing" 21 | 22 | "github.com/digitalocean/go-smbios/smbios" 23 | ) 24 | 25 | func TestStreamIntegration(t *testing.T) { 26 | if goos := runtime.GOOS; goos != "linux" { 27 | t.Skipf("skipping on non-Linux platform: %q", goos) 28 | } 29 | 30 | rc, ep, err := smbios.Stream() 31 | if err != nil { 32 | if os.IsPermission(err) { 33 | t.Skipf("skipping, permission denied while reading SMBIOS stream: %v", err) 34 | } 35 | 36 | return 37 | } 38 | defer rc.Close() 39 | 40 | d := smbios.NewDecoder(rc) 41 | ss, err := d.Decode() 42 | if err != nil { 43 | t.Fatalf("failed to decode structures: %v", err) 44 | } 45 | 46 | major, minor, rev := ep.Version() 47 | addr, size := ep.Table() 48 | 49 | // Assume SMBIOS version 2+, assume non-zero table address and size. 50 | if major < 2 { 51 | t.Fatalf("unexpected major version: %d", major) 52 | } 53 | if addr == 0 { 54 | t.Fatal("expected non-zero table address") 55 | } 56 | if size == 0 { 57 | t.Fatal("expected non-zero table size") 58 | } 59 | 60 | // Show some info in the test output. 61 | t.Logf("SMBIOS %d.%d.%d - table: address: %#x, size: %d\n", 62 | major, minor, rev, addr, size) 63 | 64 | // Assume we find BIOS and end of table types. 65 | var foundBIOS, foundEOT bool 66 | for _, s := range ss { 67 | switch s.Header.Type { 68 | case 0: 69 | foundBIOS = true 70 | t.Logf("BIOS: %#v", s) 71 | case 127: 72 | foundEOT = true 73 | t.Logf(" EOT: %#v", s) 74 | } 75 | } 76 | 77 | if !foundBIOS { 78 | t.Fatal("did not find BIOS information") 79 | } 80 | if !foundEOT { 81 | t.Fatal("did not find end of table") 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /cmd/lsdimms/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Command lsdimms lists memory DIMM information from SMBIOS. 16 | package main 17 | 18 | import ( 19 | "encoding/binary" 20 | "fmt" 21 | "log" 22 | 23 | "github.com/digitalocean/go-smbios/smbios" 24 | ) 25 | 26 | func main() { 27 | // Find SMBIOS data in operating system-specific location. 28 | rc, ep, err := smbios.Stream() 29 | if err != nil { 30 | log.Fatalf("failed to open stream: %v", err) 31 | } 32 | // Be sure to close the stream! 33 | defer rc.Close() 34 | 35 | // Decode SMBIOS structures from the stream. 36 | d := smbios.NewDecoder(rc) 37 | ss, err := d.Decode() 38 | if err != nil { 39 | log.Fatalf("failed to decode structures: %v", err) 40 | } 41 | 42 | major, minor, rev := ep.Version() 43 | fmt.Printf("SMBIOS %d.%d.%d\n", major, minor, rev) 44 | 45 | for _, s := range ss { 46 | // Only look at memory devices. 47 | if s.Header.Type != 17 { 48 | continue 49 | } 50 | 51 | // Code based on: https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf. 52 | 53 | // TODO: this should go in a new package in go-smbios for parsing specific structures. 54 | 55 | // Only parse the DIMM size. 56 | dimmSize := int(binary.LittleEndian.Uint16(s.Formatted[8:10])) 57 | 58 | if dimmSize == 0 { 59 | fmt.Printf("[% 3s] empty\n", s.Strings[0]) 60 | continue 61 | } 62 | 63 | //If the DIMM size is 32GB or greater, we need to parse the extended field. 64 | // Spec says 0x7fff in regular size field means we should parse the extended. 65 | if dimmSize == 0x7fff { 66 | dimmSize = int(binary.LittleEndian.Uint32(s.Formatted[24:28])) 67 | } 68 | 69 | // The granularity in which the value is specified 70 | // depends on the setting of the most-significant bit (bit 71 | // 15). If the bit is 0, the value is specified in megabyte 72 | // units; if the bit is 1, the value is specified in kilobyte 73 | // units. 74 | // 75 | // Little endian MSB for uint16 is in second byte. 76 | unit := "KB" 77 | if s.Formatted[9]&0x80 == 0 { 78 | unit = "MB" 79 | } 80 | 81 | fmt.Printf("[% 3s] DIMM: %d %s\n", s.Strings[0], dimmSize, unit) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-smbios [![Build Status](https://travis-ci.org/digitalocean/go-smbios.svg?branch=master)](https://travis-ci.org/digitalocean/go-smbios) [![GoDoc](https://godoc.org/github.com/digitalocean/go-smbios/smbios?status.svg)](https://godoc.org/github.com/digitalocean/go-smbios/smbios) [![Go Report Card](https://goreportcard.com/badge/github.com/digitalocean/go-smbios)](https://goreportcard.com/report/github.com/digitalocean/go-smbios) 2 | ========= 3 | 4 | Package `smbios` provides detection and access to System Management BIOS (SMBIOS) 5 | and Desktop Management Interface (DMI) data and structures. Apache 2.0 Licensed. 6 | 7 | Introduction 8 | ------------ 9 | 10 | [SMBIOS](https://en.wikipedia.org/wiki/System_Management_BIOS) is a standard 11 | mechanism for fetching BIOS and hardware information from within an operating 12 | system. It shares some similarities with the older DMI standard, and the two are 13 | often confused. 14 | 15 | To install this package, run: 16 | 17 | ``` 18 | $ go get github.com/digitalocean/go-smbios/smbios 19 | ``` 20 | 21 | This package is based on the [SMBIOS 3.1.1 specification](https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf), 22 | but should work with SMBIOS 2.0 and above. 23 | 24 | In general, it's up to hardware manufacturers to properly populate SMBIOS data, 25 | and many structures may not be populated correctly or at all; especially on 26 | consumer hardware. 27 | 28 | The `smbios.Structure` types created by this package can be decoded using the 29 | structure information in the SMBIOS specification. In the future, some common 30 | structures may be parsed and readily available by using this package. 31 | 32 | Supported operating systems and their SMBIOS retrieval mechanisms include: 33 | 34 | - DragonFlyBSD (/dev/mem) 35 | - FreeBSD (/dev/mem) 36 | - Linux (sysfs and /dev/mem) 37 | - NetBSD (/dev/mem) 38 | - OpenBSD (/dev/mem) 39 | - Solaris (/dev/mem) 40 | - Windows (GetSystemFirmwareTable) 41 | 42 | At this time, macOS is not supported, as it does not expose 43 | SMBIOS information in the same way as the supported operating systems. Pull 44 | requests are welcome to add support for additional operating systems. 45 | 46 | Example 47 | ------- 48 | 49 | See `cmd/lssmbios` for a runnable example. Note that retrieving SMBIOS 50 | information is a privileged operation. On Linux, you may invoke the binary 51 | as root directly, or apply the `CAP_DAC_OVERRIDE` capability to enable reading 52 | the information without superuser access. 53 | 54 | Here's the gist of it: 55 | 56 | ```go 57 | // Find SMBIOS data in operating system-specific location. 58 | rc, ep, err := smbios.Stream() 59 | if err != nil { 60 | log.Fatalf("failed to open stream: %v", err) 61 | } 62 | // Be sure to close the stream! 63 | defer rc.Close() 64 | 65 | // Decode SMBIOS structures from the stream. 66 | d := smbios.NewDecoder(rc) 67 | ss, err := d.Decode() 68 | if err != nil { 69 | log.Fatalf("failed to decode structures: %v", err) 70 | } 71 | 72 | // Determine SMBIOS version and table location from entry point. 73 | major, minor, rev := ep.Version() 74 | addr, size := ep.Table() 75 | 76 | fmt.Printf("SMBIOS %d.%d.%d - table: address: %#x, size: %d\n", 77 | major, minor, rev, addr, size) 78 | 79 | for _, s := range ss { 80 | fmt.Println(s) 81 | } 82 | ``` 83 | -------------------------------------------------------------------------------- /smbios/stream_memory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "io" 21 | "io/ioutil" 22 | "os" 23 | ) 24 | 25 | const ( 26 | // devMem is the UNIX-like system memory device location used to 27 | // find SMBIOS information. 28 | devMem = "/dev/mem" 29 | 30 | // SMBIOS specification indicates that the entry point should exist 31 | // between these two memory addresses. 32 | startAddr = 0x000f0000 33 | endAddr = 0x000fffff 34 | ) 35 | 36 | // memoryStream reads the SMBIOS entry point and structure stream from 37 | // an io.ReadSeeker (usually system memory). 38 | // 39 | // memoryStream is an entry point for tests. 40 | func memoryStream(rs io.ReadSeeker, startAddr, endAddr int) (io.ReadCloser, EntryPoint, error) { 41 | // Try to find the entry point. 42 | addr, err := findEntryPoint(rs, startAddr, endAddr) 43 | if err != nil { 44 | return nil, nil, err 45 | } 46 | 47 | // Found it; seek to the location of the entry point. 48 | if _, err := rs.Seek(int64(addr), io.SeekStart); err != nil { 49 | return nil, nil, err 50 | } 51 | 52 | // Read the entry point and determine where the SMBIOS table is. 53 | ep, err := ParseEntryPoint(rs) 54 | if err != nil { 55 | return nil, nil, err 56 | } 57 | 58 | // Seek to the start of the SMBIOS table. 59 | tableAddr, tableSize := ep.Table() 60 | if _, err := rs.Seek(int64(tableAddr), io.SeekStart); err != nil { 61 | return nil, nil, err 62 | } 63 | 64 | // Make a copy of the memory so we don't return a handle to system memory 65 | // to the caller. 66 | out := make([]byte, tableSize) 67 | if _, err := io.ReadFull(rs, out); err != nil { 68 | return nil, nil, err 69 | } 70 | 71 | return ioutil.NopCloser(bytes.NewReader(out)), ep, nil 72 | } 73 | 74 | // findEntryPoint attempts to locate the entry point structure in the io.ReadSeeker 75 | // using the start and end bound as hints for its location. 76 | func findEntryPoint(rs io.ReadSeeker, start, end int) (int, error) { 77 | // Begin searching at the start bound. 78 | if _, err := rs.Seek(int64(start), io.SeekStart); err != nil { 79 | return 0, err 80 | } 81 | 82 | // Iterate one "paragraph" of memory at a time until we either find the entry point 83 | // or reach the end bound. 84 | const paragraph = 16 85 | b := make([]byte, paragraph) 86 | 87 | var ( 88 | addr int 89 | found bool 90 | ) 91 | 92 | for addr = start; addr < end; addr += paragraph { 93 | if _, err := io.ReadFull(rs, b); err != nil { 94 | return 0, err 95 | } 96 | 97 | // Both the 32-bit and 64-bit entry point have a similar prefix. 98 | if bytes.HasPrefix(b, magicPrefix) { 99 | found = true 100 | break 101 | } 102 | } 103 | 104 | if !found { 105 | return 0, errors.New("no SMBIOS entry point found in memory") 106 | } 107 | 108 | // Return the exact memory location of the entry point. 109 | return addr, nil 110 | } 111 | 112 | // devMemStream reads the SMBIOS entry point and structure stream from 113 | // the UNIX-like system /dev/mem device. 114 | // 115 | // This is UNIX-like system specific, but since it doesn't employ any system 116 | // calls or OS-dependent constants, it remains in this file for simplicity. 117 | func devMemStream() (io.ReadCloser, EntryPoint, error) { 118 | mem, err := os.Open(devMem) 119 | if err != nil { 120 | return nil, nil, err 121 | } 122 | defer mem.Close() 123 | 124 | return memoryStream(mem, startAddr, endAddr) 125 | } 126 | -------------------------------------------------------------------------------- /smbios/decoder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios_test 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | 21 | "github.com/digitalocean/go-smbios/smbios" 22 | "github.com/google/go-cmp/cmp" 23 | ) 24 | 25 | func TestDecoder(t *testing.T) { 26 | tests := []struct { 27 | name string 28 | b []byte 29 | ss []*smbios.Structure 30 | ok bool 31 | }{ 32 | { 33 | name: "short header", 34 | b: []byte{0x00}, 35 | }, 36 | { 37 | name: "length too short", 38 | b: []byte{0x00, 0x00, 0x00, 0x00}, 39 | }, 40 | { 41 | name: "length too long", 42 | b: []byte{0x00, 0xff, 0x00, 0x00}, 43 | }, 44 | { 45 | name: "string not terminated", 46 | b: []byte{ 47 | 0x01, 0x04, 0x01, 0x00, 48 | 'a', 'b', 'c', 'd', 49 | }, 50 | }, 51 | { 52 | name: "no end of table", 53 | b: []byte{ 54 | 0x01, 0x04, 0x01, 0x00, 55 | 0x00, 56 | 0x00, 57 | }, 58 | }, 59 | { 60 | name: "bad second message", 61 | b: []byte{ 62 | 0x01, 0x0c, 0x02, 0x00, 63 | 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 64 | 'd', 'e', 'a', 'd', 'b', 'e', 'e', 'f', 0x00, 65 | 0x00, 66 | 67 | 0xff, 68 | }, 69 | }, 70 | { 71 | name: "OK, one, no format, no strings", 72 | b: []byte{ 73 | 127, 0x04, 0x01, 0x00, 74 | 0x00, 75 | 0x00, 76 | }, 77 | ss: []*smbios.Structure{{ 78 | Header: smbios.Header{ 79 | Type: 127, 80 | Length: 4, 81 | Handle: 1, 82 | }, 83 | }}, 84 | ok: true, 85 | }, 86 | { 87 | name: "OK, one, format, no strings", 88 | b: []byte{ 89 | 127, 0x06, 0x01, 0x00, 90 | 0x01, 0x02, 91 | 0x00, 92 | 0x00, 93 | }, 94 | ss: []*smbios.Structure{{ 95 | Header: smbios.Header{ 96 | Type: 127, 97 | Length: 6, 98 | Handle: 1, 99 | }, 100 | Formatted: []byte{0x01, 0x02}, 101 | }}, 102 | ok: true, 103 | }, 104 | { 105 | name: "OK, one, format, strings", 106 | b: []byte{ 107 | 127, 0x06, 0x01, 0x00, 108 | 0x01, 0x02, 109 | 'a', 'b', 'c', 'd', 0x00, 110 | '1', '2', '3', '4', 0x00, 111 | 0x00, 112 | }, 113 | ss: []*smbios.Structure{{ 114 | Header: smbios.Header{ 115 | Type: 127, 116 | Length: 6, 117 | Handle: 1, 118 | }, 119 | Formatted: []byte{0x01, 0x02}, 120 | Strings: []string{"abcd", "1234"}, 121 | }}, 122 | ok: true, 123 | }, 124 | { 125 | name: "OK, multiple", 126 | b: []byte{ 127 | 0x00, 0x05, 0x01, 0x00, 128 | 0xff, 129 | 0x00, 130 | 0x00, 131 | 132 | 0x01, 0x0c, 0x02, 0x00, 133 | 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 134 | 'd', 'e', 'a', 'd', 'b', 'e', 'e', 'f', 0x00, 135 | 0x00, 136 | 137 | 127, 0x06, 0x03, 0x00, 138 | 0x01, 0x02, 139 | 'a', 'b', 'c', 'd', 0x00, 140 | '1', '2', '3', '4', 0x00, 141 | 0x00, 142 | }, 143 | ss: []*smbios.Structure{ 144 | { 145 | Header: smbios.Header{ 146 | Type: 0, 147 | Length: 5, 148 | Handle: 1, 149 | }, 150 | Formatted: []byte{0xff}, 151 | }, 152 | { 153 | Header: smbios.Header{ 154 | Type: 1, 155 | Length: 12, 156 | Handle: 2, 157 | }, 158 | Formatted: []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}, 159 | Strings: []string{"deadbeef"}, 160 | }, 161 | { 162 | Header: smbios.Header{ 163 | Type: 127, 164 | Length: 6, 165 | Handle: 3, 166 | }, 167 | Formatted: []byte{0x01, 0x02}, 168 | Strings: []string{"abcd", "1234"}, 169 | }, 170 | }, 171 | ok: true, 172 | }, 173 | } 174 | 175 | for _, tt := range tests { 176 | t.Run(tt.name, func(t *testing.T) { 177 | d := smbios.NewDecoder(bytes.NewReader(tt.b)) 178 | ss, err := d.Decode() 179 | 180 | if tt.ok && err != nil { 181 | t.Fatalf("unexpected error: %v", err) 182 | } 183 | if !tt.ok && err == nil { 184 | t.Fatalf("expected an error, but none occurred: %v", err) 185 | } 186 | 187 | if diff := cmp.Diff(tt.ss, ss); diff != "" { 188 | t.Fatalf("unexpected structures (-want +got):\n%s", diff) 189 | } 190 | }) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /smbios/stream_windows_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios 16 | 17 | import ( 18 | "bytes" 19 | "encoding/hex" 20 | "io/ioutil" 21 | "testing" 22 | ) 23 | 24 | // makeRawSMBIOSData creates a buffer with a valid RawSMBIOSData struct with the 25 | // given version and stream information. 26 | func makeRawSMBIOSData(major, minor, revision byte, stream []byte) []byte { 27 | buffer := make([]byte, rawSMBIOSDataHeaderSize+len(stream)) 28 | buffer[0] = 0 29 | buffer[1] = major 30 | buffer[2] = minor 31 | buffer[3] = revision 32 | nativeEndian().PutUint32(buffer[4:8], uint32(len(stream))) 33 | copy(buffer[8:], stream) 34 | return buffer 35 | } 36 | 37 | func Test_windowsStream(t *testing.T) { 38 | const major = byte(2) 39 | const minor = byte(4) 40 | const revision = byte(1) 41 | 42 | // Note: buffer will be automatically created from the stream if it is not 43 | // explicitly set to a non-nil value. This prevents having to duplicate the 44 | // stream data in the struct definitions below for large test cases. 45 | // 46 | // Unlike in Test_memoryStream, we're not worrying about the actual decoding 47 | // of the structures here. All we care about is whether or not windowsStream 48 | // gives us back the stream data we expect. Whether or not that is valid can 49 | // be tested separately. 50 | tests := []struct { 51 | name string 52 | buffer []byte 53 | stream []byte 54 | ok bool 55 | }{ 56 | { 57 | name: "empty buffer", 58 | buffer: []byte{}, // purposefully not nil 59 | }, 60 | { 61 | name: "short buffer", 62 | buffer: []byte{0, 1, 2, 3, 4, 5, 6}, // only 7 bytes 63 | }, 64 | { 65 | name: "valid header, empty table", 66 | stream: nil, 67 | ok: true, 68 | }, 69 | { 70 | name: "length too large", 71 | buffer: func() []byte { 72 | buf := []byte{ 73 | 0, 2, 4, 1, // version 74 | 0, 0, 0, 0, // length placeholder 75 | 1, 2, 3, 4, // stream 76 | } 77 | nativeEndian().PutUint32(buf[4:8], 5) 78 | return buf 79 | }(), 80 | }, 81 | { 82 | name: "valid header and stream", 83 | stream: []byte{ 84 | 0x00, 0x05, 0x01, 0x00, 85 | 0xff, 86 | 0x00, 87 | 0x00, 88 | 89 | 0x01, 0x0c, 0x02, 0x00, 90 | 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 91 | 'd', 'e', 'a', 'd', 'b', 'e', 'e', 'f', 0x00, 92 | 0x00, 93 | 94 | 127, 0x06, 0x03, 0x00, 95 | 0x01, 0x02, 96 | 'a', 'b', 'c', 'd', 0x00, 97 | '1', '2', '3', '4', 0x00, 98 | 0x00, 99 | }, 100 | ok: true, 101 | }, 102 | { 103 | name: "buffer larger than needed", 104 | buffer: func() []byte { 105 | buf := makeRawSMBIOSData(major, minor, revision, []byte{1, 2, 3, 4}) 106 | buf = append(buf, 5, 6, 7, 8) 107 | return buf 108 | }(), 109 | stream: []byte{1, 2, 3, 4}, 110 | ok: true, 111 | }, 112 | } 113 | 114 | for _, tt := range tests { 115 | // Make buffer if not set explicitly 116 | if tt.buffer == nil { 117 | tt.buffer = makeRawSMBIOSData(major, minor, revision, tt.stream) 118 | } 119 | 120 | t.Run(tt.name, func(t *testing.T) { 121 | rc, ep, err := windowsStream(tt.buffer) 122 | 123 | if tt.ok && err != nil { 124 | t.Fatalf("unexpected error: %v", err) 125 | } 126 | if !tt.ok && err == nil { 127 | t.Fatalf("expected an error, but none occurred: %v", err) 128 | } 129 | 130 | if !tt.ok { 131 | // Don't bother doing comparison if entry point is invalid 132 | t.Logf("OK error: %v", err) 133 | return 134 | } 135 | defer rc.Close() 136 | 137 | _, streamSize := ep.Table() 138 | if streamSize != len(tt.stream) { 139 | t.Fatalf("bad stream size: got %d, wanted %d", streamSize, len(tt.stream)) 140 | } 141 | maj, min, rev := ep.Version() 142 | if maj != int(major) { 143 | t.Fatalf("bad major version: got %d, wanted %d", maj, major) 144 | } 145 | if min != int(minor) { 146 | t.Fatalf("bad minor version: got %d, wanted %d", min, minor) 147 | } 148 | if rev != int(revision) { 149 | t.Fatalf("bad revision: got %d, wanted %d", rev, revision) 150 | } 151 | 152 | streamData, err := ioutil.ReadAll(rc) 153 | if err != nil { 154 | t.Fatalf("failed to read stream: %v", err) 155 | } 156 | if len(streamData) != len(tt.stream) { 157 | t.Fatalf("bad stream data: got %d bytes, wanted %d", len(streamData), len(tt.stream)) 158 | } 159 | if bytes.Compare(tt.stream, streamData) != 0 { 160 | t.Fatalf( 161 | "stream data different:\nwant: %s\ngot : %s", 162 | hex.EncodeToString(tt.stream), 163 | hex.EncodeToString(streamData), 164 | ) 165 | } 166 | }) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /smbios/stream_memory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "fmt" 21 | "math" 22 | "testing" 23 | 24 | "github.com/google/go-cmp/cmp" 25 | ) 26 | 27 | func Test_memoryStream(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | b []byte 31 | ss []*Structure 32 | ok bool 33 | }{ 34 | { 35 | name: "empty", 36 | b: nil, 37 | }, 38 | { 39 | name: "magic before first paragraph", 40 | b: makeMemory( 41 | []byte{'_', 'S', 'M', '_'}, 42 | nil, 43 | nil, 44 | ), 45 | }, 46 | { 47 | name: "magic after last paragraph", 48 | b: makeMemory( 49 | nil, 50 | nil, 51 | []byte{'_', 'S', 'M', '_'}, 52 | ), 53 | }, 54 | { 55 | name: "64, OK", 56 | b: func() []byte { 57 | // Just enough information to point to an address 58 | // that contains the structure stream. 59 | const addr = 0x00f0 60 | epb := mustMarshalEntryPoint(&EntryPoint64Bit{ 61 | StructureTableMaxSize: 512, 62 | StructureTableAddress: addr, 63 | }) 64 | 65 | // Place entry point in searchable range. 66 | b := makeMemory( 67 | nil, 68 | epb, 69 | nil, 70 | ) 71 | 72 | // Structure stream, placed starting at the address 73 | // specified in entry point. 74 | stream := []byte{ 75 | 0x00, 0x05, 0x01, 0x00, 76 | 0xff, 77 | 0x00, 78 | 0x00, 79 | 80 | 0x01, 0x0c, 0x02, 0x00, 81 | 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 82 | 'd', 'e', 'a', 'd', 'b', 'e', 'e', 'f', 0x00, 83 | 0x00, 84 | 85 | 127, 0x06, 0x03, 0x00, 86 | 0x01, 0x02, 87 | 'a', 'b', 'c', 'd', 0x00, 88 | '1', '2', '3', '4', 0x00, 89 | 0x00, 90 | } 91 | 92 | copy(b[addr:], stream) 93 | 94 | return b 95 | }(), 96 | ss: []*Structure{ 97 | { 98 | Header: Header{ 99 | Type: 0, 100 | Length: 5, 101 | Handle: 1, 102 | }, 103 | Formatted: []byte{0xff}, 104 | }, 105 | { 106 | Header: Header{ 107 | Type: 1, 108 | Length: 12, 109 | Handle: 2, 110 | }, 111 | Formatted: []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}, 112 | Strings: []string{"deadbeef"}, 113 | }, 114 | { 115 | Header: Header{ 116 | Type: 127, 117 | Length: 6, 118 | Handle: 3, 119 | }, 120 | Formatted: []byte{0x01, 0x02}, 121 | Strings: []string{"abcd", "1234"}, 122 | }, 123 | }, 124 | ok: true, 125 | }, 126 | } 127 | 128 | for _, tt := range tests { 129 | t.Run(tt.name, func(t *testing.T) { 130 | rs := bytes.NewReader(tt.b) 131 | 132 | rc, _, err := memoryStream(rs, start, end) 133 | 134 | if tt.ok && err != nil { 135 | t.Fatalf("unexpected error: %v", err) 136 | } 137 | if !tt.ok && err == nil { 138 | t.Fatalf("expected an error, but none occurred: %v", err) 139 | } 140 | 141 | if !tt.ok { 142 | // Don't bother doing comparison if entry point is invalid. 143 | t.Logf("OK error: %v", err) 144 | return 145 | } 146 | defer rc.Close() 147 | 148 | ss, err := NewDecoder(rc).Decode() 149 | if err != nil { 150 | t.Fatalf("failed to decode structures: %v", err) 151 | } 152 | 153 | if diff := cmp.Diff(tt.ss, ss); diff != "" { 154 | t.Fatalf("unexpected structures (-want +got):\n%s", diff) 155 | } 156 | }) 157 | } 158 | } 159 | 160 | // Memory addresses used to start and stop searching for entry points. 161 | const ( 162 | start = 0x0010 163 | end = 0xfff0 164 | ) 165 | 166 | func makeMemory(before, in, after []byte) []byte { 167 | b := make([]byte, math.MaxUint16) 168 | 169 | copy(b[0x0000:start], before) 170 | copy(b[start:0xfff0], in) 171 | copy(b[end:0xffff], after) 172 | 173 | return b 174 | } 175 | 176 | func mustMarshalEntryPoint(ep EntryPoint) []byte { 177 | switch x := ep.(type) { 178 | case *EntryPoint64Bit: 179 | return marshal64(x) 180 | default: 181 | // TODO(mdlayher): expand with 32-bit entry point. 182 | panic(fmt.Sprintf("entry point marshaling not implemented for %T", ep)) 183 | } 184 | } 185 | 186 | func marshal64(ep *EntryPoint64Bit) []byte { 187 | b := make([]byte, expLen64) 188 | 189 | copy(b[0:5], magic64) 190 | b[6] = expLen64 191 | 192 | b[7] = ep.Major 193 | b[8] = ep.Minor 194 | b[9] = ep.Revision 195 | b[10] = ep.EntryPointRevision 196 | b[11] = ep.Reserved 197 | binary.LittleEndian.PutUint32(b[12:16], ep.StructureTableMaxSize) 198 | binary.LittleEndian.PutUint64(b[16:24], ep.StructureTableAddress) 199 | 200 | var chk uint8 201 | for i := range b { 202 | // Explicitly skip the checksum byte for computation. 203 | if i == chkIndex64 { 204 | continue 205 | } 206 | 207 | chk += b[i] 208 | } 209 | 210 | // Produce the correct checksum for the entry point. 211 | b[5] = uint8(256 - int(chk)) 212 | 213 | return b 214 | } 215 | -------------------------------------------------------------------------------- /smbios/stream_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "errors" 21 | "fmt" 22 | "io" 23 | "io/ioutil" 24 | "syscall" 25 | "unsafe" 26 | ) 27 | 28 | // firmwareTableProviderSigRSMB is the identifier for the raw SMBIOS firmware table 29 | // provider. 30 | // It is equal to the ASCII characters 'RSMB' packed into a uint32. 31 | // In the C++ example code in the MSDN documentation, this is specified using 32 | // multi-byte character literals, which are automatically coerced to an integer by 33 | // the C++ compiler. 34 | const firmwareTableProviderSigRSMB uint32 = 0x52534d42 35 | 36 | // smbiosDataHeaderSize is size of the "header" (non-variable) part of the 37 | // RawSMBIOSData struct. This serves as both the offset to the actual 38 | // SMBIOS table data, and the minimum possible size of a valid RawSMBIOSDATA 39 | // struct (with a table length of 0). 40 | const rawSMBIOSDataHeaderSize = 8 41 | 42 | var ( 43 | libKernel32 = syscall.NewLazyDLL("kernel32.dll") 44 | 45 | // MSDN Documentation for GetSystemFirmwareTable: 46 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724379(v=vs.85).aspx 47 | procGetSystemFirmwareTable = libKernel32.NewProc("GetSystemFirmwareTable") 48 | ) 49 | 50 | // nativeEndian returns the native byte order of this system. 51 | func nativeEndian() binary.ByteOrder { 52 | // Determine endianness by interpreting a uint16 as a byte slice. 53 | v := uint16(1) 54 | b := *(*[2]byte)(unsafe.Pointer(&v)) 55 | 56 | if b[0] == 1 { 57 | return binary.LittleEndian 58 | } 59 | 60 | return binary.BigEndian 61 | } 62 | 63 | // windowsStream parses the data returned from GetSystemFirmwareTable('RSMB',...) 64 | // and returns a stream and entrypoint that can be used to decode the system's 65 | // SMBIOS table. 66 | // 67 | // When calling GetSystemFirmwareTable with FirmwareTableProviderSignature = "RSMB", 68 | // Windows will write a RawSMBIOSData struct into the output buffer. 69 | // Thus, `buf` is expected to contain a valid RawSMBIOSData structure. 70 | // 71 | // From windows.h: 72 | // 73 | // struct RawSMBIOSData { 74 | // BYTE Used20CallingMethod; 75 | // BYTE SMBIOSMajorVersion; 76 | // BYTE SMBIOSMinorVersion; 77 | // BYTE DMIRevision; 78 | // DWORD Length; 79 | // BYTE SMBIOSTableData[]; 80 | // } 81 | // 82 | // Note: a DWORD is equivalent to a uint32 83 | // See: https://msdn.microsoft.com/en-us/library/cc230318.aspx 84 | func windowsStream(buf []byte) (io.ReadCloser, EntryPoint, error) { 85 | bufLen := uint32(len(buf)) 86 | 87 | // Do an additional check to make sure the actual amount written is sane. 88 | if bufLen < rawSMBIOSDataHeaderSize { 89 | return nil, nil, fmt.Errorf("GetSystemFirmwareTable wrote less data than expected: wrote %d bytes, expected at least 8 bytes", bufLen) 90 | } 91 | 92 | tableSize := nativeEndian().Uint32(buf[4:8]) 93 | if rawSMBIOSDataHeaderSize+tableSize > bufLen { 94 | return nil, nil, errors.New("reported SMBIOS table size exceeds buffer") 95 | } 96 | 97 | entryPoint := &WindowsEntryPoint{ 98 | MajorVersion: buf[1], 99 | MinorVersion: buf[2], 100 | Revision: buf[3], 101 | Size: tableSize, 102 | } 103 | 104 | tableBuff := buf[rawSMBIOSDataHeaderSize : rawSMBIOSDataHeaderSize+tableSize] 105 | 106 | return ioutil.NopCloser(bytes.NewReader(tableBuff)), entryPoint, nil 107 | } 108 | 109 | func stream() (io.ReadCloser, EntryPoint, error) { 110 | // Call first with empty buffer to get size. 111 | r1, _, err := procGetSystemFirmwareTable.Call( 112 | uintptr(firmwareTableProviderSigRSMB), // FirmwareTableProviderSignature = 'RSMB' 113 | 0, // FirmwareTableID = 0 114 | 0, // pFirmwareTableBuffer = NULL 115 | 0, // BufferSize = 0 116 | ) 117 | 118 | // LazyProc.Call will always return err != nil, so we need to check the primary 119 | // return value (r1) to determine whether or not an error occurred. 120 | // In this case, r1 should contain the size of the needed buffer, so it will only 121 | // be 0 if the function call failed for some reason. 122 | // 123 | // Godoc for LazyProc.Call: 124 | // https://golang.org/pkg/syscall/?GOOS=windows&GOARCH=amd64#LazyProc.Call 125 | if r1 == 0 { 126 | return nil, nil, fmt.Errorf("failed to determine size of buffer needed: %v", err) 127 | } 128 | if r1 < rawSMBIOSDataHeaderSize { 129 | return nil, nil, fmt.Errorf("reported buffer size smaller than expected: reported %d, expected >= 8", r1) 130 | } 131 | 132 | bufferSize := uint32(r1) 133 | buffer := make([]byte, bufferSize) 134 | 135 | r1, _, err = procGetSystemFirmwareTable.Call( 136 | uintptr(firmwareTableProviderSigRSMB), // FirmwareTableProviderSignature = 'RSMB' 137 | 0, // FirmwareTableID = 0 138 | uintptr(unsafe.Pointer(&buffer[0])), // pFirmwareTableBuffer = &buffer 139 | uintptr(bufferSize), // BufferSize = bufferSize 140 | ) 141 | bytesWritten := uint32(r1) 142 | 143 | // Check for the two possible failure cases documented in API: 144 | if bytesWritten > bufferSize { 145 | return nil, nil, fmt.Errorf("buffer size was too small, somehow: have %d bytes, Windows wanted %d bytes", bufferSize, bytesWritten) 146 | } 147 | if bytesWritten == 0 { 148 | return nil, nil, fmt.Errorf("failed to read SMBIOS data: %v", err) 149 | } 150 | 151 | // At this point, bytesWritten <= bufferSize, which means the call succeeded as 152 | // per the MSDN documentation. 153 | 154 | return windowsStream(buffer[:bytesWritten]) 155 | } 156 | -------------------------------------------------------------------------------- /smbios/decoder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "encoding/binary" 21 | "io" 22 | ) 23 | 24 | const ( 25 | // headerLen is the length of the Header structure. 26 | headerLen = 4 27 | 28 | // typeEndOfTable indicates the end of a stream of Structures. 29 | typeEndOfTable = 127 30 | ) 31 | 32 | var ( 33 | // Byte slices used to help parsing string-sets. 34 | null = []byte{0x00} 35 | endStringSet = []byte{0x00, 0x00} 36 | ) 37 | 38 | // A Decoder decodes Structures from a stream. 39 | type Decoder struct { 40 | br *bufio.Reader 41 | b []byte 42 | } 43 | 44 | // Stream locates and opens a stream of SMBIOS data and the SMBIOS entry 45 | // point from an operating system-specific location. The stream must be 46 | // closed after decoding to free its resources. 47 | // 48 | // If no suitable location is found, an error is returned. 49 | func Stream() (io.ReadCloser, EntryPoint, error) { 50 | rc, ep, err := stream() 51 | if err != nil { 52 | return nil, nil, err 53 | } 54 | 55 | // The io.ReadCloser from stream could be any one of a number of types 56 | // depending on the source of the SMBIOS stream information. 57 | // 58 | // To prevent the caller from potentially tampering with something dangerous 59 | // like mmap'd memory by using a type assertion, we make the io.ReadCloser 60 | // into an opaque and unexported type to prevent type assertion. 61 | return &opaqueReadCloser{rc: rc}, ep, nil 62 | } 63 | 64 | // NewDecoder creates a Decoder which decodes Structures from the input stream. 65 | func NewDecoder(r io.Reader) *Decoder { 66 | return &Decoder{ 67 | br: bufio.NewReader(r), 68 | b: make([]byte, 1024), 69 | } 70 | } 71 | 72 | // Decode decodes Structures from the Decoder's stream until an End-of-table 73 | // structure is found. 74 | func (d *Decoder) Decode() ([]*Structure, error) { 75 | var ss []*Structure 76 | 77 | for { 78 | s, err := d.next() 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | // End-of-table structure indicates end of stream. 84 | ss = append(ss, s) 85 | if s.Header.Type == typeEndOfTable { 86 | break 87 | } 88 | } 89 | 90 | return ss, nil 91 | } 92 | 93 | // next decodes the next Structure from the stream. 94 | func (d *Decoder) next() (*Structure, error) { 95 | h, err := d.parseHeader() 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | // Length of formatted section is length specified by header, minus 101 | // the length of the header itself. 102 | l := int(h.Length) - headerLen 103 | fb, err := d.parseFormatted(l) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | ss, err := d.parseStrings() 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | return &Structure{ 114 | Header: *h, 115 | Formatted: fb, 116 | Strings: ss, 117 | }, nil 118 | } 119 | 120 | // parseHeader parses a Structure's Header from the stream. 121 | func (d *Decoder) parseHeader() (*Header, error) { 122 | if _, err := io.ReadFull(d.br, d.b[:headerLen]); err != nil { 123 | return nil, err 124 | } 125 | 126 | return &Header{ 127 | Type: d.b[0], 128 | Length: d.b[1], 129 | Handle: binary.LittleEndian.Uint16(d.b[2:4]), 130 | }, nil 131 | } 132 | 133 | // parseFormatted parses a Structure's formatted data from the stream. 134 | func (d *Decoder) parseFormatted(l int) ([]byte, error) { 135 | // Guard against malformed input length. 136 | if l < 0 { 137 | return nil, io.ErrUnexpectedEOF 138 | } 139 | if l == 0 { 140 | // No formatted data. 141 | return nil, nil 142 | } 143 | 144 | if _, err := io.ReadFull(d.br, d.b[:l]); err != nil { 145 | return nil, err 146 | } 147 | 148 | // Make a copy to free up the internal buffer. 149 | fb := make([]byte, len(d.b[:l])) 150 | copy(fb, d.b[:l]) 151 | 152 | return fb, nil 153 | } 154 | 155 | // parseStrings parses a Structure's strings from the stream, if they 156 | // are present. 157 | func (d *Decoder) parseStrings() ([]string, error) { 158 | term, err := d.br.Peek(2) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | // If no string-set present, discard delimeter and end parsing. 164 | if bytes.Equal(term, endStringSet) { 165 | if _, err := d.br.Discard(2); err != nil { 166 | return nil, err 167 | } 168 | 169 | return nil, nil 170 | } 171 | 172 | var ss []string 173 | for { 174 | s, more, err := d.parseString() 175 | if err != nil { 176 | return nil, err 177 | } 178 | 179 | // When final string is received, end parse loop. 180 | ss = append(ss, s) 181 | if !more { 182 | break 183 | } 184 | } 185 | 186 | return ss, nil 187 | } 188 | 189 | // parseString parses a single string from the stream, and returns if 190 | // any more strings are present. 191 | func (d *Decoder) parseString() (str string, more bool, err error) { 192 | // We initially read bytes because it's more efficient to manipulate bytes 193 | // and allocate a string once we're all done. 194 | // 195 | // Strings are null-terminated. 196 | raw, err := d.br.ReadBytes(0x00) 197 | if err != nil { 198 | return "", false, err 199 | } 200 | 201 | b := bytes.TrimRight(raw, "\x00") 202 | 203 | peek, err := d.br.Peek(1) 204 | if err != nil { 205 | return "", false, err 206 | } 207 | 208 | if !bytes.Equal(peek, null) { 209 | // Next byte isn't null; more strings to come. 210 | return string(b), true, nil 211 | } 212 | 213 | // If two null bytes appear in a row, end of string-set. 214 | // Discard the null and indicate no more strings. 215 | if _, err := d.br.Discard(1); err != nil { 216 | return "", false, err 217 | } 218 | 219 | return string(b), false, nil 220 | } 221 | 222 | var _ io.ReadCloser = &opaqueReadCloser{} 223 | 224 | // An opaqueReadCloser masks the type of the underlying io.ReadCloser to 225 | // prevent type assertions. 226 | type opaqueReadCloser struct { 227 | rc io.ReadCloser 228 | } 229 | 230 | func (rc *opaqueReadCloser) Read(b []byte) (int, error) { return rc.rc.Read(b) } 231 | func (rc *opaqueReadCloser) Close() error { return rc.rc.Close() } 232 | -------------------------------------------------------------------------------- /smbios/entrypoint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios_test 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | 21 | "github.com/digitalocean/go-smbios/smbios" 22 | "github.com/google/go-cmp/cmp" 23 | ) 24 | 25 | func TestParseEntryPoint(t *testing.T) { 26 | tests := []struct { 27 | name string 28 | b []byte 29 | ep smbios.EntryPoint 30 | major, minor, revision int 31 | addr, size int 32 | ok bool 33 | }{ 34 | { 35 | name: "short magic", 36 | b: []byte{0x00}, 37 | }, 38 | { 39 | name: "unknown magic", 40 | b: []byte{0xff, 0xff, 0xff, 0xff}, 41 | }, 42 | { 43 | name: "32, short entry point", 44 | b: []byte{ 45 | '_', 'S', 'M', '_', 46 | }, 47 | }, 48 | { 49 | name: "32, bad length", 50 | b: []byte{ 51 | '_', 'S', 'M', '_', 52 | 0x00, 53 | 0xff, // 255 length 54 | 0x00, 55 | 0x00, 56 | 0x00, 0x00, 57 | 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 59 | '_', 'F', 'O', 'O', '_', 60 | 0x00, 61 | 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 63 | 0x00, 0x00, 64 | 0x00, 65 | }, 66 | }, 67 | { 68 | name: "32, bad intermediate anchor", 69 | b: []byte{ 70 | '_', 'S', 'M', '_', 71 | 0x00, 72 | 31, 73 | 0x00, 74 | 0x00, 75 | 0x00, 0x00, 76 | 0x00, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 78 | '_', 'F', 'O', 'O', '_', 79 | 0x00, 80 | 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 83 | 0x00, 84 | }, 85 | }, 86 | { 87 | name: "32, bad checksum", 88 | b: []byte{ 89 | '_', 'S', 'M', '_', 90 | 0x00, // 0 checksum 91 | 31, 92 | 0x00, 93 | 0x00, 94 | 0x00, 0x00, 95 | 0x00, 96 | 0x00, 0x00, 0x00, 0x00, 0x00, 97 | '_', 'D', 'M', 'I', '_', 98 | 0x00, 99 | 0x00, 0x00, 100 | 0x00, 0x00, 0x00, 0x00, 101 | 0x00, 0x00, 102 | 0x00, 103 | }, 104 | }, 105 | { 106 | name: "32, OK", 107 | b: []byte{ 108 | '_', 'S', 'M', '_', 109 | 0xa4, 110 | 0x1f, 111 | 0x2, 112 | 0x8, 113 | 0xd4, 114 | 0x1, 0x0, 115 | 0x0, 0x0, 0x0, 0x0, 0x0, 116 | '_', 'D', 'M', 'I', '_', 117 | 0x95, 118 | 0x5f, 0xf, 119 | 0x0, 0x90, 0xf0, 0x7a, 120 | 0x43, 0x0, 121 | 0x28, 122 | }, 123 | ep: &smbios.EntryPoint32Bit{ 124 | Anchor: "_SM_", 125 | Checksum: 0xa4, 126 | Length: 0x1f, 127 | Major: 0x02, 128 | Minor: 0x08, 129 | MaxStructureSize: 0x01d4, 130 | IntermediateAnchor: "_DMI_", 131 | IntermediateChecksum: 0x95, 132 | StructureTableLength: 0x0f5f, 133 | StructureTableAddress: 0x7af09000, 134 | NumberStructures: 0x43, 135 | BCDRevision: 0x28, 136 | }, 137 | major: 2, minor: 8, revision: 0, 138 | addr: 0x7af09000, size: 0x0f5f, 139 | ok: true, 140 | }, 141 | { 142 | name: "32, OK, trailing data", 143 | b: []byte{ 144 | '_', 'S', 'M', '_', 145 | 0xa4, 146 | 0x20, 147 | 0x2, 148 | 0x8, 149 | 0xd4, 150 | 0x1, 0x0, 151 | 0x0, 0x0, 0x0, 0x0, 0x0, 152 | '_', 'D', 'M', 'I', '_', 153 | 0x95, 154 | 0x5f, 0xf, 155 | 0x0, 0x90, 0xf0, 0x7a, 156 | 0x43, 0x0, 157 | 0x28, 158 | 0xff, 159 | }, 160 | ep: &smbios.EntryPoint32Bit{ 161 | Anchor: "_SM_", 162 | Checksum: 0xa4, 163 | Length: 0x20, 164 | Major: 0x02, 165 | Minor: 0x08, 166 | MaxStructureSize: 0x01d4, 167 | IntermediateAnchor: "_DMI_", 168 | IntermediateChecksum: 0x95, 169 | StructureTableLength: 0x0f5f, 170 | StructureTableAddress: 0x7af09000, 171 | NumberStructures: 0x43, 172 | BCDRevision: 0x28, 173 | }, 174 | major: 2, minor: 8, revision: 0, 175 | addr: 0x7af09000, size: 0x0f5f, 176 | ok: true, 177 | }, 178 | { 179 | name: "64, short entry point", 180 | b: []byte{ 181 | '_', 'S', 'M', '3', '_', 182 | }, 183 | }, 184 | { 185 | name: "64, bad length", 186 | b: []byte{ 187 | '_', 'S', 'M', '3', '_', 188 | 0x00, 189 | 0xff, // 255 length 190 | 0x00, 191 | 0x00, 192 | 0x00, 193 | 0x00, 194 | 0x00, 195 | 0x00, 0x00, 0x00, 0x00, 196 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 197 | }, 198 | }, 199 | { 200 | name: "64, bad checksum", 201 | b: []byte{ 202 | '_', 'S', 'M', '3', '_', 203 | 0x00, // 0 checksum 204 | 0x18, 205 | 0x00, 206 | 0x00, 207 | 0x00, 208 | 0x00, 209 | 0x00, 210 | 0x00, 0x00, 0x00, 0x00, 211 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 212 | }, 213 | }, 214 | { 215 | name: "64, OK", 216 | b: []byte{ 217 | '_', 'S', 'M', '3', '_', 218 | 0x86, 219 | 0x18, 220 | 0x3, 221 | 0x0, 222 | 0x0, 223 | 0x1, 224 | 0x0, 225 | 0x53, 0x9, 0x0, 0x0, 226 | 0xb0, 0xb3, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 227 | }, 228 | ep: &smbios.EntryPoint64Bit{ 229 | Anchor: "_SM3_", 230 | Checksum: 0x86, 231 | Length: 0x18, 232 | Major: 0x03, 233 | EntryPointRevision: 0x01, 234 | StructureTableMaxSize: 0x0953, 235 | StructureTableAddress: 0x0eb3b0, 236 | }, 237 | major: 3, minor: 0, revision: 0, 238 | addr: 0x0eb3b0, size: 0x0953, 239 | ok: true, 240 | }, 241 | { 242 | name: "64, OK, trailing data", 243 | b: []byte{ 244 | '_', 'S', 'M', '3', '_', 245 | 0x86, 246 | 0x19, 247 | 0x3, 248 | 0x0, 249 | 0x0, 250 | 0x1, 251 | 0x0, 252 | 0x53, 0x9, 0x0, 0x0, 253 | 0xb0, 0xb3, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 254 | 0xff, 255 | }, 256 | ep: &smbios.EntryPoint64Bit{ 257 | Anchor: "_SM3_", 258 | Checksum: 0x86, 259 | Length: 0x19, 260 | Major: 0x03, 261 | EntryPointRevision: 0x01, 262 | StructureTableMaxSize: 0x0953, 263 | StructureTableAddress: 0x0eb3b0, 264 | }, 265 | major: 3, minor: 0, revision: 0, 266 | addr: 0x0eb3b0, size: 0x0953, 267 | ok: true, 268 | }, 269 | } 270 | 271 | for _, tt := range tests { 272 | t.Run(tt.name, func(t *testing.T) { 273 | ep, err := smbios.ParseEntryPoint(bytes.NewReader(tt.b)) 274 | 275 | if tt.ok && err != nil { 276 | t.Fatalf("unexpected error: %v", err) 277 | } 278 | if !tt.ok && err == nil { 279 | t.Fatalf("expected an error, but none occurred: %v", err) 280 | } 281 | 282 | if !tt.ok { 283 | // Don't bother doing comparison if entry point is invalid. 284 | t.Logf("OK error: %v", err) 285 | return 286 | } 287 | 288 | if diff := cmp.Diff(tt.ep, ep); diff != "" { 289 | t.Fatalf("unexpected entry point (-want +got):\n%s", diff) 290 | } 291 | 292 | major, minor, revision := ep.Version() 293 | wantVersion := []int{tt.major, tt.minor, tt.revision} 294 | gotVersion := []int{major, minor, revision} 295 | 296 | if diff := cmp.Diff(wantVersion, gotVersion); diff != "" { 297 | t.Fatalf("unexpected SMBIOS version (-want +got):\n%s", diff) 298 | } 299 | 300 | addr, size := ep.Table() 301 | wantTable := []int{tt.addr, tt.size} 302 | gotTable := []int{addr, size} 303 | 304 | if diff := cmp.Diff(wantTable, gotTable); diff != "" { 305 | t.Fatalf("unexpected SMBIOS table info (-want +got):\n%s", diff) 306 | } 307 | }) 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /smbios/entrypoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 DigitalOcean. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smbios 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "fmt" 21 | "io" 22 | "io/ioutil" 23 | ) 24 | 25 | // Anchor strings used to detect entry points. 26 | var ( 27 | // Used when searching for an entry point in memory. 28 | magicPrefix = []byte("_SM") 29 | 30 | // Used to determine specific entry point types. 31 | magic32 = []byte("_SM_") 32 | magic64 = []byte("_SM3_") 33 | magicDMI = []byte("_DMI_") 34 | ) 35 | 36 | // An EntryPoint is an SMBIOS entry point. EntryPoints contain various 37 | // properties about SMBIOS. 38 | // 39 | // Use a type assertion to access detailed EntryPoint information. 40 | type EntryPoint interface { 41 | // Table returns the memory address and maximum size of the SMBIOS table. 42 | Table() (address, size int) 43 | 44 | // Version returns the system's SMBIOS version. 45 | Version() (major, minor, revision int) 46 | } 47 | 48 | // ParseEntryPoint parses an EntryPoint from the input stream. 49 | func ParseEntryPoint(r io.Reader) (EntryPoint, error) { 50 | // Prevent unbounded reads since this structure should be small. 51 | b, err := ioutil.ReadAll(io.LimitReader(r, 64)) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | if l := len(b); l < 4 { 57 | return nil, fmt.Errorf("too few bytes for SMBIOS entry point magic: %d", l) 58 | } 59 | 60 | switch { 61 | case bytes.HasPrefix(b, magic32): 62 | return parse32(b) 63 | case bytes.HasPrefix(b, magic64): 64 | return parse64(b) 65 | } 66 | 67 | return nil, fmt.Errorf("unrecognized SMBIOS entry point magic: %v", b[0:4]) 68 | } 69 | 70 | var _ EntryPoint = &EntryPoint32Bit{} 71 | 72 | // EntryPoint32Bit is the SMBIOS 32-bit Entry Point structure, used starting 73 | // in SMBIOS 2.1. 74 | type EntryPoint32Bit struct { 75 | Anchor string 76 | Checksum uint8 77 | Length uint8 78 | Major uint8 79 | Minor uint8 80 | MaxStructureSize uint16 81 | EntryPointRevision uint8 82 | FormattedArea [5]byte 83 | IntermediateAnchor string 84 | IntermediateChecksum uint8 85 | StructureTableLength uint16 86 | StructureTableAddress uint32 87 | NumberStructures uint16 88 | BCDRevision uint8 89 | } 90 | 91 | // Table implements EntryPoint. 92 | func (e *EntryPoint32Bit) Table() (address, size int) { 93 | return int(e.StructureTableAddress), int(e.StructureTableLength) 94 | } 95 | 96 | // Version implements EntryPoint. 97 | func (e *EntryPoint32Bit) Version() (major, minor, revision int) { 98 | return int(e.Major), int(e.Minor), 0 99 | } 100 | 101 | // parse32 parses an EntryPoint32Bit from b. 102 | func parse32(b []byte) (*EntryPoint32Bit, error) { 103 | l := len(b) 104 | 105 | // Correct minimum length as of SMBIOS 3.1.1. 106 | const expLen = 31 107 | if l < expLen { 108 | return nil, fmt.Errorf("expected SMBIOS 32-bit entry point minimum length of at least %d, but got: %d", expLen, l) 109 | } 110 | 111 | // Allow more data in the buffer than the actual length, for when the 112 | // entry point is being read from system memory. 113 | length := b[5] 114 | if l < int(length) { 115 | return nil, fmt.Errorf("expected SMBIOS 32-bit entry point actual length of at least %d, but got: %d", length, l) 116 | } 117 | 118 | // Look for intermediate anchor with DMI magic. 119 | iAnchor := b[16:21] 120 | if !bytes.Equal(iAnchor, magicDMI) { 121 | return nil, fmt.Errorf("incorrect DMI magic in SMBIOS 32-bit entry point: %v", iAnchor) 122 | } 123 | 124 | // Entry point checksum occurs at index 4, compute and verify it. 125 | const epChkIndex = 4 126 | epChk := b[epChkIndex] 127 | if err := checksum(epChk, epChkIndex, b[:length]); err != nil { 128 | return nil, err 129 | } 130 | 131 | // Since we already computed the checksum for the outer entry point, 132 | // no real need to compute it for the intermediate entry point. 133 | 134 | ep := &EntryPoint32Bit{ 135 | Anchor: string(b[0:4]), 136 | Checksum: epChk, 137 | Length: length, 138 | Major: b[6], 139 | Minor: b[7], 140 | MaxStructureSize: binary.LittleEndian.Uint16(b[8:10]), 141 | EntryPointRevision: b[10], 142 | IntermediateAnchor: string(iAnchor), 143 | IntermediateChecksum: b[21], 144 | StructureTableLength: binary.LittleEndian.Uint16(b[22:24]), 145 | StructureTableAddress: binary.LittleEndian.Uint32(b[24:28]), 146 | NumberStructures: binary.LittleEndian.Uint16(b[28:30]), 147 | BCDRevision: b[30], 148 | } 149 | copy(ep.FormattedArea[:], b[10:15]) 150 | 151 | return ep, nil 152 | } 153 | 154 | var _ EntryPoint = &EntryPoint64Bit{} 155 | 156 | // EntryPoint64Bit is the SMBIOS 64-bit Entry Point structure, used starting 157 | // in SMBIOS 3.0. 158 | type EntryPoint64Bit struct { 159 | Anchor string 160 | Checksum uint8 161 | Length uint8 162 | Major uint8 163 | Minor uint8 164 | Revision uint8 165 | EntryPointRevision uint8 166 | Reserved uint8 167 | StructureTableMaxSize uint32 168 | StructureTableAddress uint64 169 | } 170 | 171 | // Table implements EntryPoint. 172 | func (e *EntryPoint64Bit) Table() (address, size int) { 173 | return int(e.StructureTableAddress), int(e.StructureTableMaxSize) 174 | } 175 | 176 | // Version implements EntryPoint. 177 | func (e *EntryPoint64Bit) Version() (major, minor, revision int) { 178 | return int(e.Major), int(e.Minor), int(e.Revision) 179 | } 180 | 181 | const ( 182 | // expLen64 is the expected minimum length of a 64-bit entry point. 183 | // Correct minimum length as of SMBIOS 3.1.1. 184 | expLen64 = 24 185 | 186 | // chkIndex64 is the index of the checksum byte in a 64-bit entry point. 187 | chkIndex64 = 5 188 | ) 189 | 190 | // parse64 parses an EntryPoint64Bit from b. 191 | func parse64(b []byte) (*EntryPoint64Bit, error) { 192 | l := len(b) 193 | 194 | // Ensure expected minimum length. 195 | if l < expLen64 { 196 | return nil, fmt.Errorf("expected SMBIOS 64-bit entry point minimum length of at least %d, but got: %d", expLen64, l) 197 | } 198 | 199 | // Allow more data in the buffer than the actual length, for when the 200 | // entry point is being read from system memory. 201 | length := b[6] 202 | if l < int(length) { 203 | return nil, fmt.Errorf("expected SMBIOS 64-bit entry point actual length of at least %d, but got: %d", length, l) 204 | } 205 | 206 | // Checksum occurs at index 5, compute and verify it. 207 | chk := b[chkIndex64] 208 | if err := checksum(chk, chkIndex64, b); err != nil { 209 | return nil, err 210 | } 211 | 212 | return &EntryPoint64Bit{ 213 | Anchor: string(b[0:5]), 214 | Checksum: chk, 215 | Length: length, 216 | Major: b[7], 217 | Minor: b[8], 218 | Revision: b[9], 219 | EntryPointRevision: b[10], 220 | Reserved: b[11], 221 | StructureTableMaxSize: binary.LittleEndian.Uint32(b[12:16]), 222 | StructureTableAddress: binary.LittleEndian.Uint64(b[16:24]), 223 | }, nil 224 | } 225 | 226 | // checksum computes the checksum of b using the starting value of start, and 227 | // skipping the checksum byte which occurs at index chkIndex. 228 | // 229 | // checksum assumes that b has already had its bounds checked. 230 | func checksum(start uint8, chkIndex int, b []byte) error { 231 | chk := start 232 | for i := range b { 233 | // Checksum computation does not include index of checksum byte. 234 | if i == chkIndex { 235 | continue 236 | } 237 | 238 | chk += b[i] 239 | } 240 | 241 | if chk != 0 { 242 | return fmt.Errorf("invalid entry point checksum %#02x from initial checksum %#02x", chk, start) 243 | } 244 | 245 | return nil 246 | } 247 | 248 | // WindowsEntryPoint contains SMBIOS Table entry point data returned from 249 | // GetSystemFirmwareTable. As raw access to the underlying memory is not given, 250 | // the full breadth of information is not available. 251 | type WindowsEntryPoint struct { 252 | Size uint32 253 | MajorVersion byte 254 | MinorVersion byte 255 | Revision byte 256 | } 257 | 258 | // Table implements EntryPoint. The returned address will always be 0, as it 259 | // is not returned by GetSystemFirmwareTable. 260 | func (e *WindowsEntryPoint) Table() (address, size int) { 261 | return 0, int(e.Size) 262 | } 263 | 264 | // Version implements EntryPoint. 265 | func (e *WindowsEntryPoint) Version() (major, minor, revision int) { 266 | return int(e.MajorVersion), int(e.MinorVersion), int(e.Revision) 267 | } 268 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | ============== 3 | 4 | _Version 2.0, January 2004_ 5 | _<>_ 6 | 7 | ### Terms and Conditions for use, reproduction, and distribution 8 | 9 | #### 1. Definitions 10 | 11 | “License” shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | “Legal Entity” shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, “control” means **(i)** the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the 22 | outstanding shares, or **(iii)** beneficial ownership of such entity. 23 | 24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising 25 | permissions granted by this License. 26 | 27 | “Source” form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | “Object” form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | “Work” shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | “Derivative Works” shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | “Contribution” shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | “submitted” means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as “Not a Contribution.” 58 | 59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | #### 2. Grant of Copyright License 64 | 65 | Subject to the terms and conditions of this License, each Contributor hereby 66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 67 | irrevocable copyright license to reproduce, prepare Derivative Works of, 68 | publicly display, publicly perform, sublicense, and distribute the Work and such 69 | Derivative Works in Source or Object form. 70 | 71 | #### 3. Grant of Patent License 72 | 73 | Subject to the terms and conditions of this License, each Contributor hereby 74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 75 | irrevocable (except as stated in this section) patent license to make, have 76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 77 | such license applies only to those patent claims licensable by such Contributor 78 | that are necessarily infringed by their Contribution(s) alone or by combination 79 | of their Contribution(s) with the Work to which such Contribution(s) was 80 | submitted. If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this License 84 | for that Work shall terminate as of the date such litigation is filed. 85 | 86 | #### 4. Redistribution 87 | 88 | You may reproduce and distribute copies of the Work or Derivative Works thereof 89 | in any medium, with or without modifications, and in Source or Object form, 90 | provided that You meet the following conditions: 91 | 92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of 93 | this License; and 94 | * **(b)** You must cause any modified files to carry prominent notices stating that You 95 | changed the files; and 96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, 97 | all copyright, patent, trademark, and attribution notices from the Source form 98 | of the Work, excluding those notices that do not pertain to any part of the 99 | Derivative Works; and 100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any 101 | Derivative Works that You distribute must include a readable copy of the 102 | attribution notices contained within such NOTICE file, excluding those notices 103 | that do not pertain to any part of the Derivative Works, in at least one of the 104 | following places: within a NOTICE text file distributed as part of the 105 | Derivative Works; within the Source form or documentation, if provided along 106 | with the Derivative Works; or, within a display generated by the Derivative 107 | Works, if and wherever such third-party notices normally appear. The contents of 108 | the NOTICE file are for informational purposes only and do not modify the 109 | License. You may add Your own attribution notices within Derivative Works that 110 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 111 | provided that such additional attribution notices cannot be construed as 112 | modifying the License. 113 | 114 | You may add Your own copyright statement to Your modifications and may provide 115 | additional or different license terms and conditions for use, reproduction, or 116 | distribution of Your modifications, or for any such Derivative Works as a whole, 117 | provided Your use, reproduction, and distribution of the Work otherwise complies 118 | with the conditions stated in this License. 119 | 120 | #### 5. Submission of Contributions 121 | 122 | Unless You explicitly state otherwise, any Contribution intentionally submitted 123 | for inclusion in the Work by You to the Licensor shall be under the terms and 124 | conditions of this License, without any additional terms or conditions. 125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 126 | any separate license agreement you may have executed with Licensor regarding 127 | such Contributions. 128 | 129 | #### 6. Trademarks 130 | 131 | This License does not grant permission to use the trade names, trademarks, 132 | service marks, or product names of the Licensor, except as required for 133 | reasonable and customary use in describing the origin of the Work and 134 | reproducing the content of the NOTICE file. 135 | 136 | #### 7. Disclaimer of Warranty 137 | 138 | Unless required by applicable law or agreed to in writing, Licensor provides the 139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, 140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 141 | including, without limitation, any warranties or conditions of TITLE, 142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 143 | solely responsible for determining the appropriateness of using or 144 | redistributing the Work and assume any risks associated with Your exercise of 145 | permissions under this License. 146 | 147 | #### 8. Limitation of Liability 148 | 149 | In no event and under no legal theory, whether in tort (including negligence), 150 | contract, or otherwise, unless required by applicable law (such as deliberate 151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 152 | liable to You for damages, including any direct, indirect, special, incidental, 153 | or consequential damages of any character arising as a result of this License or 154 | out of the use or inability to use the Work (including but not limited to 155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 156 | any and all other commercial damages or losses), even if such Contributor has 157 | been advised of the possibility of such damages. 158 | 159 | #### 9. Accepting Warranty or Additional Liability 160 | 161 | While redistributing the Work or Derivative Works thereof, You may choose to 162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 163 | other liability obligations and/or rights consistent with this License. However, 164 | in accepting such obligations, You may act only on Your own behalf and on Your 165 | sole responsibility, not on behalf of any other Contributor, and only if You 166 | agree to indemnify, defend, and hold each Contributor harmless for any liability 167 | incurred by, or claims asserted against, such Contributor by reason of your 168 | accepting any such warranty or additional liability. 169 | 170 | _END OF TERMS AND CONDITIONS_ 171 | 172 | ### APPENDIX: How to apply the Apache License to your work 173 | 174 | To apply the Apache License to your work, attach the following boilerplate 175 | notice, with the fields enclosed by brackets `[]` replaced with your own 176 | identifying information. (Don't include the brackets!) The text should be 177 | enclosed in the appropriate comment syntax for the file format. We also 178 | recommend that a file or class name and description of purpose be included on 179 | the same “printed page” as the copyright notice for easier identification within 180 | third-party archives. 181 | 182 | Copyright [yyyy] [name of copyright owner] 183 | 184 | Licensed under the Apache License, Version 2.0 (the "License"); 185 | you may not use this file except in compliance with the License. 186 | You may obtain a copy of the License at 187 | 188 | http://www.apache.org/licenses/LICENSE-2.0 189 | 190 | Unless required by applicable law or agreed to in writing, software 191 | distributed under the License is distributed on an "AS IS" BASIS, 192 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 193 | See the License for the specific language governing permissions and 194 | limitations under the License. 195 | 196 | --------------------------------------------------------------------------------