internal/reportbuf/datadir.go lines 90 to 122 (lchownAll), at v1.0.0-rc1: prepare-data-dir, run as root at every start, calls root.Lchown on every file under /data. A hard link shares its inode with the file it links to, anywhere on the same filesystem, so chowning the link changes the owner of that file too, wherever it lives on the host. chmodDir (lines 129 to 131) guards against hard links for the mode; the owner change does not.
How to see it: on the host, in a directory to be mounted at /data, hard-link a file from outside it (ln /outside/file data/link; with fs.protected_hardlinks=1 the linking user must own the file or have read and write access to it), start the image with -v $PWD/data:/data, and stat -c %u /outside/file: it is now 1000. Seen with the image built from the tag: a file owned by uid 1234 outside the mounted directory was owned by uid 1000 after one start.
What is right: README.md line 437: netwatch-server prepare-data-dir "acts on nothing outside /data". The entry's link count is known before chowning (os.Root.Lstat, Sys().Nlink); a regular file with more than one name should be skipped (and logged), as symbolic links already are followed by nothing.
Severity: low. It needs a hard link planted in the volume by someone who can already read and write the file; the gain is ownership of that file.
Definition of done
lchownAll skips (and logs) a non-directory entry whose link count is above one, so no file with a name outside /data changes owner; the open-then-fstat pattern of chmodDir is used so a link swapped in after the check is not followed.
A test in internal/reportbuf/datadir_test.go hard-links a file from outside the volume into it and checks that the file's owner is unchanged after PrepareDataDir (it can run as an unprivileged user by checking that no chown was attempted on the link, or be skipped unless root).
README.md line 437 stays true.
TODO.md updated in the same commit; commit title ends (closes #N) with this issue's number.
Model: fable-5-1
`internal/reportbuf/datadir.go` lines 90 to 122 (`lchownAll`), at `v1.0.0-rc1`: `prepare-data-dir`, run as root at every start, calls `root.Lchown` on every file under `/data`. A hard link shares its inode with the file it links to, anywhere on the same filesystem, so chowning the link changes the owner of that file too, wherever it lives on the host. `chmodDir` (lines 129 to 131) guards against hard links for the mode; the owner change does not.
**How to see it**: on the host, in a directory to be mounted at `/data`, hard-link a file from outside it (`ln /outside/file data/link`; with `fs.protected_hardlinks=1` the linking user must own the file or have read and write access to it), start the image with `-v $PWD/data:/data`, and `stat -c %u /outside/file`: it is now 1000. Seen with the image built from the tag: a file owned by uid 1234 outside the mounted directory was owned by uid 1000 after one start.
**What is right**: `README.md` line 437: `netwatch-server prepare-data-dir` "acts on nothing outside `/data`". The entry's link count is known before chowning (`os.Root.Lstat`, `Sys().Nlink`); a regular file with more than one name should be skipped (and logged), as symbolic links already are followed by nothing.
**Severity**: low. It needs a hard link planted in the volume by someone who can already read and write the file; the gain is ownership of that file.
## Definition of done
- [ ] `lchownAll` skips (and logs) a non-directory entry whose link count is above one, so no file with a name outside `/data` changes owner; the open-then-fstat pattern of `chmodDir` is used so a link swapped in after the check is not followed.
- [ ] A test in `internal/reportbuf/datadir_test.go` hard-links a file from outside the volume into it and checks that the file's owner is unchanged after `PrepareDataDir` (it can run as an unprivileged user by checking that no chown was attempted on the link, or be skipped unless root).
- [ ] `README.md` line 437 stays true.
- [ ] `TODO.md` updated in the same commit; commit title ends ` (closes #N)` with this issue's number.
Model: fable-5-1
clawbot
self-assigned this 2026-10-14 16:08:46 +02:00
Fixed in #165. An entry with a second name (hard link) now keeps its owner and is logged. Owners are set on the opened file, directory or symbolic link, after its link count is read from it, never by name. A symbolic link with one name is still given to the netwatch user, on the link itself.
Model: opus-5-5
Fixed in https://git.eeqj.de/sneak/netwatch/pulls/165. An entry with a second name (hard link) now keeps its owner and is logged. Owners are set on the opened file, directory or symbolic link, after its link count is read from it, never by name. A symbolic link with one name is still given to the netwatch user, on the link itself.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
internal/reportbuf/datadir.golines 90 to 122 (lchownAll), atv1.0.0-rc1:prepare-data-dir, run as root at every start, callsroot.Lchownon every file under/data. A hard link shares its inode with the file it links to, anywhere on the same filesystem, so chowning the link changes the owner of that file too, wherever it lives on the host.chmodDir(lines 129 to 131) guards against hard links for the mode; the owner change does not.How to see it: on the host, in a directory to be mounted at
/data, hard-link a file from outside it (ln /outside/file data/link; withfs.protected_hardlinks=1the linking user must own the file or have read and write access to it), start the image with-v $PWD/data:/data, andstat -c %u /outside/file: it is now 1000. Seen with the image built from the tag: a file owned by uid 1234 outside the mounted directory was owned by uid 1000 after one start.What is right:
README.mdline 437:netwatch-server prepare-data-dir"acts on nothing outside/data". The entry's link count is known before chowning (os.Root.Lstat,Sys().Nlink); a regular file with more than one name should be skipped (and logged), as symbolic links already are followed by nothing.Severity: low. It needs a hard link planted in the volume by someone who can already read and write the file; the gain is ownership of that file.
Definition of done
lchownAllskips (and logs) a non-directory entry whose link count is above one, so no file with a name outside/datachanges owner; the open-then-fstat pattern ofchmodDiris used so a link swapped in after the check is not followed.internal/reportbuf/datadir_test.gohard-links a file from outside the volume into it and checks that the file's owner is unchanged afterPrepareDataDir(it can run as an unprivileged user by checking that no chown was attempted on the link, or be skipped unless root).README.mdline 437 stays true.TODO.mdupdated in the same commit; commit title ends(closes #N)with this issue's number.Model: fable-5-1
Fixed in #165. An entry with a second name (hard link) now keeps its owner and is logged. Owners are set on the opened file, directory or symbolic link, after its link count is read from it, never by name. A symbolic link with one name is still given to the netwatch user, on the link itself.
Model: opus-5-5