From 6dc1d95baaace5268020d02a05a516dfc7e9755a Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 4 Oct 2026 21:22:24 +0200 Subject: [PATCH] docs: Security page (mirrors SECURITY.md at v0.3.0); untrusted-input note; complete Creation row Co-Authored-By: Claude Opus 5.5 --- docs/index.md | 10 ++++++- docs/security.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 docs/security.md diff --git a/docs/index.md b/docs/index.md index 6be1226..e1f6767 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,7 +24,7 @@ m.Transpose().Shape() // [3 2] | Area | Functions / methods | | --- | --- | -| Creation | `Zeros`, `Ones`, `Full`, `Arange`, `Linspace`, `Eye`, `FromData` | +| Creation | `New`, `Zeros`, `Ones`, `Full`, `Arange`, `Linspace`, `Eye`, `Identity`, `FromData` | | Views & shape | `Slice` (`All`/`R`/`Rng`/`From`/`To`/`Step`), `Reshape`, `Ravel`, `Transpose`, `Copy` | | Elementwise | `Add`/`Sub`/`Mul`/`Div` (+`*Scalar`, +`*Into`), `Map`, `Neg`, `Abs` | | Ufuncs | `Sqrt`, `Exp`, `Log`/`Log2`/`Log10`, `Sin`/`Cos`/`Tan`, `Floor`/`Ceil`/`Round`, `Square`, `Power` | @@ -34,6 +34,14 @@ m.Transpose().Shape() // [3 2] | Linear algebra | `MatMul`, `Dot`, `Inner`, `Outer` | | Memory reuse | `NewWorkspace`, `Use`, `Reset`, `Detach` | +## Untrusted input (v0.3.0) + +A shape the library cannot represent is an error wrapping `ErrShapeMismatch`, +for results as well as inputs: `(2^32, 0) @ (0, 2^32)` holds no data, yet its +result would have 2^64 elements, so `MatMul` refuses it, as NumPy does. Empty +arrays with a huge axis cost nothing, and every assembly call is guarded and +fence-tested. See [Security](security.md). + ## Loops: `Workspace` (v0.2.0) Every operation returns a new array, and in a loop those results are garbage diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 0000000..13313c1 --- /dev/null +++ b/docs/security.md @@ -0,0 +1,69 @@ +# Security + +> Mirrored from [`SECURITY.md`](https://github.com/go-ndarray/ndarray/blob/main/SECURITY.md) at v0.3.0. + +## Reporting + +Report a vulnerability privately through GitHub's **Report a vulnerability** +button on [the repository](https://github.com/go-ndarray/ndarray/security) (Security → Advisories). Please do not open a public +issue for it. + +## What the library guarantees + +go-ndarray is pure Go (`CGO_ENABLED=0`) except for its SIMD kernels, which are +Plan 9 assembly generated by [go-asmgen](https://github.com/go-asmgen/asmgen) +for amd64, arm64, ppc64le and loong64. Those kernels are the only code Go's +bounds checks do not cover, so they are held to the following: + +- **Every wrapper checks the reach of its kernel.** Before an assembly call, the + Go wrapper indexes the last element of every operand the kernel reads or + writes (`internal/kernels/{reduce,gemm}_.go`). A caller's mistake is an + index-out-of-range panic in Go, never an access outside a slice. + `TestWrappersRefuseShortOperands` passes each wrapper an operand one element + short and requires the panic. +- **The kernels stay inside their slices.** `TestKernelsStayInsideTheirSlices` + and `TestGemmMicroStaysInsideItsOperands` ([`internal/kernels/fence_test.go`](https://github.com/go-ndarray/ndarray/blob/main/internal/kernels/fence_test.go), + Linux and macOS) map each operand flush against a `PROT_NONE` page, once + ending at it and once starting after one, for every length from 1 to 70 and + every GEMM depth from 1 to 9. A read or write one element past either end is a + fault. The fence itself was checked by a kernel deliberately reading one + element too far, which faults on amd64 (4 KiB pages), arm64 (16 KiB), loong64 + (16 KiB) and ppc64le (64 KiB). Prefetch instructions may point past the end: + they are hints and do not fault. +- **Shapes are validated, results included.** A shape whose element count, or + whose size in bytes, does not fit in an `int` is an error wrapping + `ErrShapeMismatch`, even when a zero dimension makes it empty, as in NumPy. + This applies to the shapes operations compute: `MatMul`, `Dot`, `Inner`, + `Concatenate`, `Stack`, broadcasting and `Linspace`. `Outer` has no error + result and panics instead. +- **Empty arrays cost nothing.** Operations on an empty array with a huge axis, + such as `(2^58, 0)`, return at once instead of walking that axis. + `TestEmptyArraysWithHugeAxesReturnPromptly` covers concatenation, the axis + reductions and the scans. + +The minimum Go version (`go` in `go.mod`) is kept on a release that fixes the +standard library's known vulnerabilities; `govulncheck ./...` reports none. + +## Audit log + +**2026-10-04 (v0.3.0).** + +- `govulncheck` found no reachable vulnerability. At `go 1.26.4`, 10 standard + library vulnerabilities were in modules the library requires but did not + call; `go 1.26.6` fixes all of them. +- Found and fixed: + - `Concatenate` summed the axis lengths without an overflow check, so 17 + arrays of shape `(2^59, 0)` gave a **negative** dimension. + - `MatMul` of `(2^32, 0)` by `(0, 2^32)` returned a `(2^32, 2^32)` array whose + `Size()` was 0. A product wrapping to −1 panicked in `makeslice`. + - `Linspace` with a count above the limit panicked in `makeslice`. + - `Stack` of 9 arrays `(2^58, 0)` returned `(9, 2^58, 0)`, which `New` + rejects. + - Reducing `(2^58, 0)` over axis 0, concatenating it along its last axis, or + scanning `(0, 2^58)` walked the 2^58-long axis: effectively a hang. +- Hardened: the operand checks in front of every assembly call (above). The + fence test found no out-of-bounds access in any kernel, on real amd64 + (Zen 3), arm64, ppc64le (POWER9) and loong64 (3C5000L, with LASX). +- Cost: none measurable. A single-core A/B on Zen 3 (10 interleaved rounds) + measured `MatMul` 256², `Dot1D` and `AddInto` 16 Ki within ±2%, inside the + run-to-run spread. diff --git a/mkdocs.yml b/mkdocs.yml index b4a486d..78080ac 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,6 +45,7 @@ nav: - Home: index.md - Roadmap: roadmap.md - Performance: performance.md + - Security: security.md markdown_extensions: - admonition