Repository: cilium/ebpf
Pull request #1737 (2025-03-31 14:17:44)

map, program: always sanitize names passed to the kernel
The kernel only allows a restricted set of characters when naming a map or program. So far we've required the user to call SanitizeName manually to ensure that the Name is valid.

Always sanitize names and unexport SanitizeName. The ELF reader still calls sanitizeName to avoid changing the output too much.


Patch:
diff --git a/Makefile b/Makefile
index e0fe974..18e9bb0 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,10 @@ UIDGID := $(shell stat -c '%u:%g' ${REPODIR})
 # Prefer podman if installed, otherwise use docker.
 # Note: Setting the var at runtime will always override.
 CONTAINER_ENGINE ?= $(if $(shell command -v podman), podman, docker)
-CONTAINER_RUN_ARGS ?= $(if $(filter ${CONTAINER_ENGINE}, podman), --log-driver=none, --user "${UIDGID}")
+CONTAINER_RUN_ARGS ?= $(if $(filter ${CONTAINER_ENGINE}, podman), \
+		--log-driver=none \
+		-v "$(shell go env GOCACHE)":/root/.cache/go-build \
+		-v "$(shell go env GOMODCACHE)":/go/pkg/mod, --user "${UIDGID}")
 
 IMAGE := $(shell cat ${REPODIR}/testdata/docker/IMAGE)
 VERSION := $(shell cat ${REPODIR}/testdata/docker/VERSION)
@@ -70,7 +73,7 @@ container-all:
 # (debug) Drop the user into a shell inside the container as root.
 # Set BPF2GO_ envs to make 'make generate' just work.
 container-shell:
-	${CONTAINER_ENGINE} run --rm -ti \
+	${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \
 		-v "${REPODIR}":/ebpf -w /ebpf \
 		--env BPF2GO_CC="$(CLANG)" \
 		--env BPF2GO_FLAGS="-fdebug-prefix-map=/ebpf=. $(CFLAGS)" \
diff --git a/cmd/bpf2go/main.go b/cmd/bpf2go/main.go
index 9408be1..3732333 100644
--- a/cmd/bpf2go/main.go
+++ b/cmd/bpf2go/main.go
@@ -13,6 +13,7 @@ import (
 	"regexp"
 	"slices"
 	"sort"
+	"strconv"
 	"strings"
 
 	"github.com/cilium/ebpf"
@@ -57,7 +58,8 @@ func run(stdout io.Writer, args []string) (err error) {
 }
 
 type bpf2go struct {
-	stdout io.Writer
+	stdout  io.Writer
+	verbose bool
 	// Absolute path to a .c file.
 	sourceFile string
 	// Absolute path to a directory where .go are written
@@ -89,12 +91,19 @@ type bpf2go struct {
 	makeBase string
 }
 
+func (b2g *bpf2go) Debugln(a ...any) {
+	if b2g.verbose {
+		fmt.Fprintln(b2g.stdout, a...)
+	}
+}
+
 func newB2G(stdout io.Writer, args []string) (*bpf2go, error) {
 	b2g := &bpf2go{
 		stdout: stdout,
 	}
 
 	fs := flag.NewFlagSet("bpf2go", flag.ContinueOnError)
+	fs.BoolVar(&b2g.verbose, "verbose", getBool("V", false), "Enable verbose logging ($V)")
 	fs.StringVar(&b2g.cc, "cc", getEnv("BPF2GO_CC", "clang"),
 		"`binary` used to compile C to BPF ($BPF2GO_CC)")
 	fs.StringVar(&b2g.strip, "strip", getEnv("BPF2GO_STRIP", ""),
@@ -117,6 +126,7 @@ func newB2G(stdout io.Writer, args []string) (*bpf2go, error) {
 		"suffix in generated file names such as _test (default based on $GOFILE)")
 	outDir := fs.String("output-dir", "", "target directory of generated files (defaults to current directory)")
 	outPkg := fs.String("go-package", "", "package for output go file (default as ENV GOPACKAGE)")
+
 	fs.SetOutput(b2g.stdout)
 	fs.Usage = func() {
 		fmt.Fprintf(fs.Output(), helpText, fs.Name())
@@ -273,6 +283,20 @@ func getEnv(key, defaultVal string) string {
 	return defaultVal
 }
 
+func getBool(key string, defaultVal bool) bool {
+	val, ok := os.LookupEnv(key)
+	if !ok {
+		return defaultVal
+	}
+
+	b, err := strconv.ParseBool(val)
+	if err != nil {
+		return defaultVal
+	}
+
+	return b
+}
+
 func (b2g *bpf2go) convertAll() (err error) {
 	if _, err := os.Stat(b2g.sourceFile); os.IsNotExist(err) {
 		return fmt.Errorf("file %s doesn't exist", b2g.sourceFile)
@@ -365,10 +389,10 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) {
 		return fmt.Errorf("compile: %w", err)
 	}
 
-	fmt.Fprintln(b2g.stdout, "Compiled", objFileName)
-
-	if !b2g.disableStripping {
-		fmt.Fprintln(b2g.stdout, "Stripped", objFileName)
+	if b2g.disableStripping {
+		b2g.Debugln("Compiled object", "file", objFileName)
+	} else {
+		b2g.Debugln("Compiled and stripped object", "file", objFileName)
 	}
 
 	spec, err := ebpf.LoadCollectionSpec(objFileName)
@@ -426,7 +450,7 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) {
 		return fmt.Errorf("can't write %s: %s", goFileName, err)
 	}
 
-	fmt.Fprintln(b2g.stdout, "Wrote", goFileName)
+	b2g.Debugln("Generated bpf2go binding", "file", goFileName)
 
 	if b2g.makeBase == "" {
 		return
@@ -450,7 +474,8 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) {
 		return fmt.Errorf("can't adjust dependency information: %s", err)
 	}
 
-	fmt.Fprintln(b2g.stdout, "Wrote", depFileName)
+	b2g.Debugln("Wrote dependency", "file", depFileName)
+
 	return nil
 }
 
@@ -474,7 +499,7 @@ func (b2g *bpf2go) removeOldOutputFiles(outputStem string, tgt gen.Target) error
 			return err
 		}
 
-		fmt.Fprintln(b2g.stdout, "Removed obsolete", filename)
+		b2g.Debugln("Removed obsolete output file", "file", filename)
 	}
 
 	return nil
diff --git a/docs/Pipfile.lock b/docs/Pipfile.lock
index 90d238f..3395cd8 100644
--- a/docs/Pipfile.lock
+++ b/docs/Pipfile.lock
@@ -209,7 +209,6 @@
                 "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d",
                 "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"
             ],
-            "index": "pypi",
             "markers": "python_version >= '3.7'",
             "version": "==3.1.6"
         },
@@ -315,21 +314,21 @@
         },
         "mkdocs-git-authors-plugin": {
             "hashes": [
-                "sha256:77f97c321e08a8757beb866293eb257070b11cd5a080976bc6696b249cbade4f",
-                "sha256:f6cefc4dc832865d26f7f9f944c0a8c7dc852742d79320f3800e0d97814e2a84"
+                "sha256:84b9b56c703841189c64d8ff6947034fe0a9c14a0a8f1f6255edfcfe3a56825f",
+                "sha256:f5cfaf93d08981ce25591bbaf642051ed168c3886bb96ecd2dca53f0ef1973b8"
             ],
             "index": "pypi",
             "markers": "python_version >= '3.8'",
-            "version": "==0.9.2"
+            "version": "==0.9.4"
         },
         "mkdocs-git-revision-date-localized-plugin": {
             "hashes": [
-                "sha256:439e2f14582204050a664c258861c325064d97cdc848c541e48bb034a6c4d0cb",
-                "sha256:c99377ee119372d57a9e47cff4e68f04cce634a74831c06bc89b33e456e840a1"
+                "sha256:395ae0b9eec565c78a76fffc938b70f52774bf2971245482d31aba47018c5fa9",
+                "sha256:9e37f1343d314c9cd8f9cbbf117c4bdc1d2d24a0653c5bbd7059d072f2cd0972"
             ],
             "index": "pypi",
             "markers": "python_version >= '3.8'",
-            "version": "==1.3.0"
+            "version": "==1.4.5"
         },
         "mkdocs-macros-plugin": {
             "hashes": [
@@ -342,12 +341,12 @@
         },
         "mkdocs-material": {
             "hashes": [
-                "sha256:06141bd720b0b235829bd59e8afc11d5587c35ae7fc340612d2b3f554e6a69d8",
-                "sha256:904c422ec86086144495831cee2614bb8a0092572ef579af6392b8080309d3a3"
+                "sha256:0b7f4a0145c5074cdd692e4362d232fb25ef5b23328d0ec1ab287af77cc0deff",
+                "sha256:47f21ef9cbf4f0ebdce78a2ceecaa5d413581a55141e4464902224ebbc0b1263"
             ],
             "index": "pypi",
             "markers": "python_version >= '3.8'",
-            "version": "==9.6.6"
+            "version": "==9.6.11"
         },
         "mkdocs-material-extensions": {
             "hashes": [
@@ -382,11 +381,11 @@
         },
         "platformdirs": {
             "hashes": [
-                "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907",
-                "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"
+                "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94",
+                "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"
             ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.3.6"
+            "markers": "python_version >= '3.9'",
+            "version": "==4.3.7"
         },
         "pygments": {
             "hashes": [
@@ -410,15 +409,15 @@
                 "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
                 "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
             "version": "==2.9.0.post0"
         },
         "pytz": {
             "hashes": [
-                "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57",
-                "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"
+                "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3",
+                "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"
             ],
-            "version": "==2025.1"
+            "version": "==2025.2"
         },
         "pyyaml": {
             "hashes": [
@@ -600,7 +599,7 @@
                 "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
                 "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
             "version": "==1.17.0"
         },
         "smmap": {
diff --git a/docs/ebpf/contributing/windows.md b/docs/ebpf/contributing/windows.md
index e8b0a76..e3bc32f 100644
--- a/docs/ebpf/contributing/windows.md
+++ b/docs/ebpf/contributing/windows.md
@@ -1,4 +1,4 @@
-# How to work on the Windows port
+# Working on the Windows port
 
 The library has basic support for interacting with eBPF for Windows (efW).
 Things are subject to change because eBPF for Windows has not had a stable (signed) release yet.
@@ -16,16 +16,6 @@ Things are subject to change because eBPF for Windows has not had a stable (sign
   via dynamic runtime linking. This uses the same infrastructure as CGo but
   does not require a C toolchain and is therefore trivial to distribute.
 
-## Platform specific ELFs
-
-ELFs compiled against Linux and Windows headers are not binary compatible.
-Add the following to ELFs targeting Windows until there is an
-[official way to declare the platform]:
-
-```C
-const bool __ebpf_for_windows_tag __attribute__((section(".ebpf_for_windows"))) = true;
-```
-
 ## Exported API
 
 The library only supports a subset of the full API on Windows, because the eBPF for
@@ -184,5 +174,4 @@ efW uses several layers of error codes.
 [ntosebpfext]: https://github.com/microsoft/ntosebpfext
 [access the debug version of the msvc runtime]: https://github.com/microsoft/ebpf-for-windows/issues/3872
 [msvc debug DLLs]: https://github.com/microsoft/ebpf-for-windows/blob/7005b7ff47e7281843d6b414cd69fc5a979507c8/scripts/setup-ebpf.ps1#L17-L27
-[official way to declare the platform]: https://github.com/microsoft/ebpf-for-windows/issues/3956
 [efW CI/CD]: https://github.com/microsoft/ebpf-for-windows/actions/workflows/cicd.yml?query=branch%3Amain+is%3Acompleted
diff --git a/docs/ebpf/guides/windows-support.md b/docs/ebpf/guides/windows-support.md
new file mode 100644
index 0000000..c337174
--- /dev/null
+++ b/docs/ebpf/guides/windows-support.md
@@ -0,0 +1,57 @@
+# Windows support
+
+The library has preliminary support for the [eBPF for Windows] runtime, allowing
+you to build Go applications for Windows using the same APIs as on Linux.
+
+!!! warning "Feature parity"
+    efW doesn't have feature parity with Linux. Many APIs in
+    the library will return `ErrNotSupported` in this case.
+
+!!! warning "Binary compatibility"
+    efW is not binary compatible with Linux. It is not possible
+    to compile an eBPF program for Linux and use it on Windows.
+
+## Platform specific constants
+
+efW only provides [source compatibility] with Linux.
+While certain Linux map or program types have an equivalent on Windows, they
+don't always behave the same.
+
+For this reason, the various type enumerations have completely distinct values
+on Windows, for example `WindowsHashMap` is the equivalent of `HashMap`.
+Attempting to create a `HashMap` on Windows will return an error, and vice versa.
+
+## Platform specific ELFs
+
+!!! note ""
+    Loading Windows ELFs is not supported yet.
+
+ELFs compiled against Linux and Windows headers are not binary compatible.
+Add the following to ELFs targeting Windows until there is an
+[official way to declare the platform](https://github.com/microsoft/ebpf-for-windows/issues/3956):
+
+```C
+const bool __ebpf_for_windows_tag __attribute__((section(".ebpf_for_windows"))) = true;
+```
+
+## Working with signed programs
+
+The runtime will most likely require all eBPF programs to be signed by
+Microsoft. Signing programs relies on packaging eBPF `.c` files as drivers using
+the [native code pipeline], converting bytecode into a `.sys` file.
+
+The interface to load such drivers does not allow modifying the bytecode or map
+definitions, therefore you can't interact with them via `CollectionSpec`, etc.
+Instead you must load them via `LoadCollection`:
+
+```go
+coll, err := LoadCollection("path\\to\\driver.sys")
+```
+
+The returned Collection contains Maps and Programs which you can interact with
+as usual.
+
+[eBPF for Windows]: https://github.com/microsoft/ebpf-for-windows
+[source compatibility]: https://github.com/microsoft/ebpf-for-windows?tab=readme-ov-file#2-does-this-provide-app-compatibility-with-ebpf-programs-written-for-linux
+[native code pipeline]: https://github.com/microsoft/ebpf-for-windows/blob/main/docs/NativeCodeGeneration.md
+[LoadCollection]: https://pkg.go.dev/github.com/cilium/ebpf#LoadCollection
diff --git a/docs/includes/glossary.md b/docs/includes/glossary.md
index bb1f4ae..4f6264f 100644
--- a/docs/includes/glossary.md
+++ b/docs/includes/glossary.md
@@ -17,3 +17,4 @@
 *[libbpf]: A library for writing kernel- and user space BPF programs in C, developed by the upstream Linux project.
 *[qemu]: A popular virtual machine manager.
 *[DCO]: Developer Certificate of Origin.
+*[efW]: eBPF for Windows
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index f4a1089..3f0b3d9 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -60,6 +60,7 @@ nav:
   - 'Guides':
     - 'Getting Started': guides/getting-started.md
     - 'Portable eBPF': guides/portable-ebpf.md
+    - 'Windows support': guides/windows-support.md
   - 'Concepts':
     - 'Loading eBPF Programs': concepts/loader.md
     - 'Global Variables': concepts/global-variables.md
diff --git a/elf_reader.go b/elf_reader.go
index 92076aa..0a5a0e5 100644
--- a/elf_reader.go
+++ b/elf_reader.go
@@ -742,7 +742,7 @@ func (ec *elfCode) loadMaps() error {
 			lr := io.LimitReader(r, int64(size))
 
 			spec := MapSpec{
-				Name: SanitizeName(mapName, -1),
+				Name: sanitizeName(mapName, -1),
 			}
 			switch {
 			case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
@@ -1029,7 +1029,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b
 	}
 
 	return &MapSpec{
-		Name:       SanitizeName(name, -1),
+		Name:       sanitizeName(name, -1),
 		Type:       MapType(mapType),
 		KeySize:    keySize,
 		ValueSize:  valueSize,
@@ -1156,7 +1156,7 @@ func (ec *elfCode) loadDataSections() error {
 		}
 
 		mapSpec := &MapSpec{
-			Name:       SanitizeName(sec.Name, -1),
+			Name:       sanitizeName(sec.Name, -1),
 			Type:       Array,
 			KeySize:    4,
 			ValueSize:  uint32(sec.Size),
diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go
index 10047b2..08fde8a 100644
--- a/internal/cmd/gentypes/main.go
+++ b/internal/cmd/gentypes/main.go
@@ -7,10 +7,12 @@ package main
 import (
 	"bytes"
 	"errors"
+	"flag"
 	"fmt"
 	"os"
 	"slices"
 	"sort"
+	"strconv"
 	"strings"
 
 	"github.com/cilium/ebpf/btf"
@@ -25,8 +27,40 @@ const (
 	retFd
 )
 
+var (
+	verbose bool
+)
+
+func debugln(a ...any) {
+	if verbose {
+		fmt.Println(a...)
+	}
+}
+
+func getBool(key string, defaultVal bool) bool {
+	val, ok := os.LookupEnv(key)
+	if !ok {
+		return defaultVal
+	}
+
+	b, err := strconv.ParseBool(val)
+	if err != nil {
+		return defaultVal
+	}
+
+	return b
+}
+
 func main() {
-	if err := run(os.Args[1:]); err != nil {
+	fs := flag.NewFlagSet("gentypes", flag.ContinueOnError)
+	fs.BoolVar(&verbose, "verbose", getBool("V", false), "Enable verbose logging ($V)")
+
+	if err := fs.Parse(os.Args[1:]); err != nil {
+		fmt.Fprintln(os.Stderr, "Error:", err)
+		os.Exit(1)
+	}
+
+	if err := run(fs.Args()); err != nil {
 		fmt.Fprintln(os.Stderr, "Error:", err)
 		os.Exit(1)
 	}
@@ -154,12 +188,15 @@ import (
 		return consts[i].Name < consts[j].Name
 	})
 
+	var nconsts uint32
 	w.WriteString("const (\n")
 	for _, c := range consts {
-		fmt.Println("const", c.Name)
+		debugln("const", c.Name)
 		fmt.Fprintf(w, "\t%s = %v\n", c.Name, c.Value)
+		nconsts++
 	}
 	w.WriteString(")\n")
+	fmt.Printf("Generated %d constants\n", nconsts)
 
 	// Typed constants (aka named enums)
 	enums := []struct {
@@ -188,9 +225,10 @@ import (
 		return enums[i].goType < enums[j].goType
 	})
 
+	var nenums uint32
 	enumTypes := make(map[string]btf.Type)
 	for _, o := range enums {
-		fmt.Println("enum", o.goType)
+		debugln("enum", o.goType)
 
 		var t *btf.Enum
 		if err := spec.TypeByName(o.cType, &t); err != nil {
@@ -212,7 +250,10 @@ import (
 
 		w.WriteString(decl)
 		w.WriteRune('\n')
+
+		nenums++
 	}
+	fmt.Printf("Generated %d enums\n", nenums)
 
 	// Assorted structs
 
@@ -277,8 +318,9 @@ import (
 		return structs[i].goType < structs[j].goType
 	})
 
+	var nstructs uint32
 	for _, s := range structs {
-		fmt.Println("struct", s.goType)
+		debugln("struct", s.goType)
 
 		var t *btf.Struct
 		if err := spec.TypeByName(s.cType, &t); err != nil {
@@ -288,7 +330,9 @@ import (
 		if err := outputPatchedStruct(gf, w, s.goType, t, s.patches); err != nil {
 			return nil, fmt.Errorf("output %q: %w", s.goType, err)
 		}
+		nstructs++
 	}
+	fmt.Printf("Generated %d structs\n", nstructs)
 
 	// Attrs
 
@@ -620,8 +664,9 @@ import (
 		return nil, fmt.Errorf("split bpf_attr: %w", err)
 	}
 
+	var nattrs uint32
 	for _, s := range attrs {
-		fmt.Println("attr", s.goType)
+		debugln("attr", s.goType)
 
 		t := attrTypes[s.cType]
 		if t == nil {
@@ -639,7 +684,9 @@ import (
 		case retFd:
 			fmt.Fprintf(w, "func %s(attr *%s) (*FD, error) { fd, err := BPF(%s, unsafe.Pointer(attr), unsafe.Sizeof(*attr)); if err != nil { return nil, err }; return NewFD(int(fd)) }\n\n", s.goType, goAttrType, s.cmd)
 		}
+		nattrs++
 	}
+	fmt.Printf("Generated %d attrs\n", nattrs)
 
 	// Link info type specific
 	linkInfoExtraTypes := []struct {
diff --git a/map.go b/map.go
index f288966..b2da9f6 100644
--- a/map.go
+++ b/map.go
@@ -50,8 +50,9 @@ type MapID = sys.MapID
 
 // MapSpec defines a Map.
 type MapSpec struct {
-	// Name is passed to the kernel as a debug aid. Must only contain
-	// alpha numeric and '_' characters.
+	// Name is passed to the kernel as a debug aid.
+	//
+	// Unsupported characters will be stripped.
 	Name       string
 	Type       MapType
 	KeySize    uint32
diff --git a/prog.go b/prog.go
index d1f4908..ebad9cf 100644
--- a/prog.go
+++ b/prog.go
@@ -106,8 +106,9 @@ type ProgramOptions struct {
 
 // ProgramSpec defines a Program.
 type ProgramSpec struct {
-	// Name is passed to the kernel as a debug aid. Must only contain
-	// alpha numeric and '_' characters.
+	// Name is passed to the kernel as a debug aid.
+	//
+	// Unsupported characters will be stripped.
 	Name string
 
 	// Type determines at which hook in the kernel a program will run.
diff --git a/syscalls.go b/syscalls.go
index e6f5f69..f03bed0 100644
--- a/syscalls.go
+++ b/syscalls.go
@@ -27,12 +27,12 @@ var (
 	sysErrNotSupported = sys.Error(ErrNotSupported, sys.ENOTSUPP)
 )
 
-// SanitizeName replaces all invalid characters in name with replacement.
+// sanitizeName replaces all invalid characters in name with replacement.
 // Passing a negative value for replacement will delete characters instead
 // of replacing them.
 //
 // The set of allowed characters may change over time.
-func SanitizeName(name string, replacement rune) string {
+func sanitizeName(name string, replacement rune) string {
 	return strings.Map(func(char rune) rune {
 		switch {
 		case char >= 'A' && char <= 'Z':
@@ -56,6 +56,7 @@ func maybeFillObjName(name string) sys.ObjName {
 		return sys.ObjName{}
 	}
 
+	name = sanitizeName(name, -1)
 	if errors.Is(objNameAllowsDot(), ErrNotSupported) {
 		name = strings.ReplaceAll(name, ".", "")
 	}


Test patch:
diff --git a/map_test.go b/map_test.go
index 9bb7c02..021c8d7 100644
--- a/map_test.go
+++ b/map_test.go
@@ -1362,20 +1362,23 @@ func TestMapMarshalUnsafe(t *testing.T) {
 }
 
 func TestMapName(t *testing.T) {
-	if err := haveObjName(); err != nil {
-		t.Skip(err)
-	}
+	testutils.SkipIfNotSupported(t, haveObjName())
 
-	m := createMap(t, Array, 1)
+	m := mustNewMap(t, &MapSpec{
+		Name:       "test!123",
+		Type:       Array,
+		KeySize:    4,
+		ValueSize:  4,
+		MaxEntries: 1,
+	}, nil)
 
 	var info sys.MapInfo
 	if err := sys.ObjInfo(m.fd, &info); err != nil {
 		t.Fatal(err)
 	}
 
-	if name := unix.ByteSliceToString(info.Name[:]); name != "test" {
-		t.Error("Expected name to be test, got", name)
-	}
+	name := unix.ByteSliceToString(info.Name[:])
+	qt.Assert(t, qt.Equals(name, "test123"))
 }
 
 func TestMapFromFD(t *testing.T) {
diff --git a/prog_test.go b/prog_test.go
index ab5929b..7f15ef0 100644
--- a/prog_test.go
+++ b/prog_test.go
@@ -479,20 +479,25 @@ func TestProgramWithUnsatisfiedMap(t *testing.T) {
 }
 
 func TestProgramName(t *testing.T) {
-	if err := haveObjName(); err != nil {
-		t.Skip(err)
-	}
+	testutils.SkipIfNotSupported(t, haveObjName())
 
-	prog := createBasicProgram(t)
+	prog := mustNewProgram(t, &ProgramSpec{
+		Name: "test*123",
+		Type: SocketFilter,
+		Instructions: asm.Instructions{
+			asm.LoadImm(asm.R0, 1, asm.DWord),
+			asm.Return(),
+		},
+		License: "MIT",
+	}, nil)
 
 	var info sys.ProgInfo
 	if err := sys.ObjInfo(prog.fd, &info); err != nil {
 		t.Fatal(err)
 	}
 
-	if name := unix.ByteSliceToString(info.Name[:]); name != "test" {
-		t.Errorf("Name is not test, got '%s'", name)
-	}
+	name := unix.ByteSliceToString(info.Name[:])
+	qt.Assert(t, qt.Equals(name, "test123"))
 }
 
 func TestProgramCloneNil(t *testing.T) {
diff --git a/syscalls_test.go b/syscalls_test.go
index 1642bce..f85f382 100644
--- a/syscalls_test.go
+++ b/syscalls_test.go
@@ -19,7 +19,7 @@ func TestSanitizeName(t *testing.T) {
 		"t_est":    "t_est",
 		"hörnchen": "hrnchen",
 	} {
-		qt.Assert(t, qt.Equals(SanitizeName(input, -1), want), qt.Commentf("input: %s", input))
+		qt.Assert(t, qt.Equals(sanitizeName(input, -1), want), qt.Commentf("input: %s", input))
 	}
 }