Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rc.init: fix mkchar not setting correct filemode #3624

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

thaJeztah
Copy link
Contributor

@thaJeztah thaJeztah commented Apr 13, 2021

Commit bcfb760 (#2500) rewrote the init from a shell script to a go implementation. However, unlike mknod, the active umask is applied when using Go's unix.Mknod().

This patch:

  • sets the correct mode when calling unix.Mknod()
  • temporarily overrides the umask during doMounts()

Steps I used to reproduce the original issue, and to verify the changes in this
patch (tried inside a container):

docker run -it --rm -w /app golang bash

cat > mknod.go <<'EOF'
package main

import (
    "log"

    "golang.org/x/sys/unix"
)

func main() {
    mkchar("/dev/null2", 0666, 1, 3)

    umask := unix.Umask(0000)
    defer unix.Umask(umask)
    mkchar2("/dev/null3", 0666, 1, 3)
}

// make a character device
func mkchar(path string, mode, major, minor uint32) {
    // unix.Mknod only supports int dev numbers; this is ok for us
    dev := int(unix.Mkdev(major, minor))
    err := unix.Mknod(path, mode, dev)
    if err != nil {
        if err.Error() == "file exists" {
            return
        }
        log.Printf("error making device %s: %v", path, err)
    }
}

// make a character device
func mkchar2(path string, mode, major, minor uint32) {
    // unix.Mknod only supports int dev numbers; this is ok for us
    dev := int(unix.Mkdev(major, minor))
    err := unix.Mknod(path, mode|unix.S_IFCHR, dev)
    if err != nil {
        if err.Error() == "file exists" {
            return
        }
        log.Printf("error making device %s: %v", path, err)
    }
}
EOF

Initialize module and fetch dependencies:

go mod init foo && go mod tidy

Check active umask:

umask
0022

Run the test code:

go run mknod.go

Check the results:

ls -la /dev/null*
crw-rw-rw- 1 root root 1, 3 Apr 13 11:45 /dev/null
-rw-r--r-- 1 root root    0 Apr 13 11:45 /dev/null2
crw-rw-rw- 1 root root 1, 3 Apr 13 11:45 /dev/null3

Notice that:

  • /dev/null2 (before) was created with active umask (0022) applied, and did not create a character device
  • /dev/null3 (after) has both the correct (0666) permissions and mode
    - What I did

- How I did it

- How to verify it

- Description for the changelog

- A picture of a cute animal (not mandatory but encouraged)

@thaJeztah
Copy link
Contributor Author

@djs55 @justincormack PTAL

@@ -55,13 +55,15 @@ func mountSilent(source string, target string, fstype string, flags uintptr, dat
func mkchar(path string, mode, major, minor uint32) {
// unix.Mknod only supports int dev numbers; this is ok for us
dev := int(unix.Mkdev(major, minor))
err := unix.Mknod(path, mode, dev)
err := unix.Mknod(path, mode|unix.S_IFCHR, dev)
if err != nil {
if err.Error() == "file exists" {
return
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to chmod in all cases (also in this case?) or only if we actually created?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather set the umask at the beginning of the code so the permissions are unaffected and then set it to 022 at the end for the children to inherit, rather than also calling chmod all over the place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was doubting about that; I know we had/have some code in docker that changes umask, and it caused issues for processes that were running concurrently.

But I guess that's not a problem for this case (no other code should be running concurrently 😅)

@djs55
Copy link
Contributor

djs55 commented Apr 13, 2021

Could you gofmt -w -s? I get

dave@m1 pkg % linuxkit pkg build -org dockerpinata -disable-content-trust init
Building "dockerpinata/init:7b3f8a1a279e2e861786f97444af231afddefa0b"
Error response from daemon: manifest for dockerpinata/init:7b3f8a1a279e2e861786f97444af231afddefa0b not found: manifest unknown: manifest unknown
No image pulled, continuing with build
[+] Building 1.2s (12/25)                                                                                                                                                                         
 => CACHED [internal] load remote build context                                                                                                                                              0.0s
 => CACHED copy /context /                                                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/linuxkit/alpine:e2391e0b164c57db9f6c4ae110ee84f766edc430                                                                                          0.6s
 => [mirror 1/5] FROM docker.io/linuxkit/alpine:e2391e0b164c57db9f6c4ae110ee84f766edc430@sha256:bca9e72a17c1c74cf7b28a397c643048fd055fa096a3edfe1d16365a5307b9d9                             0.0s
 => CACHED [build  2/12] RUN apk add --no-cache --initdb alpine-baselayout make gcc musl-dev git linux-headers                                                                               0.0s
 => CACHED [build  3/12] ADD usermode-helper.c ./                                                                                                                                            0.0s
 => CACHED [build  4/12] RUN LDFLAGS=-static CFLAGS=-Werror make usermode-helper                                                                                                             0.0s
 => CACHED [build  5/12] RUN apk add --no-cache go musl-dev                                                                                                                                  0.0s
 => CACHED [build  6/12] RUN [ $(uname -m) = aarch64 ] && apk add --no-cache gcc || true                                                                                                     0.0s
 => CACHED [build  7/12] COPY cmd /go/src/cmd                                                                                                                                                0.0s
 => CACHED [build  8/12] RUN go-compile.sh /go/src/cmd/init                                                                                                                                  0.0s
 => ERROR [build  9/12] RUN go-compile.sh /go/src/cmd/rc.init                                                                                                                                0.6s
------                                                                                                                                                                                            
 > [build  9/12] RUN go-compile.sh /go/src/cmd/rc.init:                                                                                                                                           
#12 0.083 gofmt...
#12 0.088 main.go
------
executor failed running [/bin/sh -c go-compile.sh /go/src/cmd/rc.init]: exit code: 1
exit status 1
dave@m1 pkg % gofmt -w -s init/cmd/rc.init/main.go 
dave@m1 pkg % git diff
diff --git a/pkg/init/cmd/rc.init/main.go b/pkg/init/cmd/rc.init/main.go
index caba3a666..d583f9a14 100644
--- a/pkg/init/cmd/rc.init/main.go
+++ b/pkg/init/cmd/rc.init/main.go
@@ -63,7 +63,7 @@ func mkchar(path string, mode, major, minor uint32) {
                log.Printf("error making device %s: %v", path, err)
        }
        // make sure we get the correct filemode, irregardless of active umask
-       _  = os.Chmod(path, os.FileMode(mode)|unix.S_IFCHR)
+       _ = os.Chmod(path, os.FileMode(mode)|unix.S_IFCHR)
 }
 
 // symlink with error warning

@thaJeztah
Copy link
Contributor Author

oh! yes, looks like I didn't enable my gofmt-on-safe for this project 😂

@thaJeztah
Copy link
Contributor Author

👍 should be fixed now

Commit bcfb760 rewrote the init from a shell
script to a go implementation. However, unlike `mknod`, the active umask is
applied when using Go's unix.Mknod().

This patch:

- sets the correct mode when calling unix.Mknod()
- temporarily overrides the umask during doMounts()

Steps I used to reproduce the original issue, and to verify the changes in this
patch (tried inside a container):

    docker run -it --rm -w /app golang bash

    cat > mknod.go <<'EOF'
    package main

    import (
        "log"

        "golang.org/x/sys/unix"
    )

    func main() {
        mkchar("/dev/null2", 0666, 1, 3)

        umask := unix.Umask(0000)
        defer unix.Umask(umask)
        mkchar2("/dev/null3", 0666, 1, 3)
    }

    // make a character device
    func mkchar(path string, mode, major, minor uint32) {
        // unix.Mknod only supports int dev numbers; this is ok for us
        dev := int(unix.Mkdev(major, minor))
        err := unix.Mknod(path, mode, dev)
        if err != nil {
            if err.Error() == "file exists" {
                return
            }
            log.Printf("error making device %s: %v", path, err)
        }
    }

    // make a character device
    func mkchar2(path string, mode, major, minor uint32) {
        // unix.Mknod only supports int dev numbers; this is ok for us
        dev := int(unix.Mkdev(major, minor))
        err := unix.Mknod(path, mode|unix.S_IFCHR, dev)
        if err != nil {
            if err.Error() == "file exists" {
                return
            }
            log.Printf("error making device %s: %v", path, err)
        }
    }
    EOF

Initialize module and fetch dependencies:

    go mod init foo && go mod tidy

Check active umask:

    umask
    0022

Run the test code:

    go run mknod.go

Check the results:

    ls -la /dev/null*
    crw-rw-rw- 1 root root 1, 3 Apr 13 11:45 /dev/null
    -rw-r--r-- 1 root root    0 Apr 13 11:45 /dev/null2
    crw-rw-rw- 1 root root 1, 3 Apr 13 11:45 /dev/null3

Notice that:

- `/dev/null2` (before) was created with active umask (`0022`) applied, and did not create a character device
- `/dev/null3` (after) has both the correct (0666) permissions and mode

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
@thaJeztah
Copy link
Contributor Author

@djs55 @justincormack updated; PTAL

@djs55
Copy link
Contributor

djs55 commented Apr 13, 2021

I've been playing with this image inside Docker Desktop. The permissions are still not what I would expect :( I added some debug:

diff --git a/pkg/init/cmd/rc.init/main.go b/pkg/init/cmd/rc.init/main.go
index caba3a666..bcae5e248 100644
--- a/pkg/init/cmd/rc.init/main.go
+++ b/pkg/init/cmd/rc.init/main.go
@@ -54,16 +54,26 @@ func mountSilent(source string, target string, fstype string, flags uintptr, dat
 // make a character device
 func mkchar(path string, mode, major, minor uint32) {
        // unix.Mknod only supports int dev numbers; this is ok for us
+       log.Printf("mkchar %s %o", path, mode)
        dev := int(unix.Mkdev(major, minor))
        err := unix.Mknod(path, mode|unix.S_IFCHR, dev)
+       // make sure we get the correct filemode, irregardless of active umask
+       err = os.Chmod(path, os.FileMode(mode)|unix.S_IFCHR)
+       log.Printf("chmod %s %o err = %v", path, mode, err)
        if err != nil {
                if err.Error() == "file exists" {
+                       log.Printf("file exists")
                        return
                }
                log.Printf("error making device %s: %v", path, err)
        }
-       // make sure we get the correct filemode, irregardless of active umask
-       _  = os.Chmod(path, os.FileMode(mode)|unix.S_IFCHR)
+       if stat, err := os.Stat(path); err == nil {
+               log.Printf("after Mknod %s has mode %o", path, stat.Mode())
+       }
+
+       if stat, err := os.Stat(path); err == nil {
+               log.Printf("after Chmod %s has mode %o", path, stat.Mode())
+       }
 }
 
 // symlink with error warning

and the logging looks correct:

2021/04/13 14:33:08 chmod /dev/tty 666 err = <nil>
2021/04/13 14:33:08 after Mknod /dev/tty has mode 410000666

but I still see

 % docker run -it --privileged --pid=host justincormack/nsenter1
/ # ls -l /dev/tty
crw-rw----    1 root     root        5,   0 Jan  1  1970 /dev/tty
/ # ls -l /proc/1/root/dev/tty
crw-rw----    1 root     root        5,   0 Jan  1  1970 /proc/1/root/dev/tty

I need to debug more.

@djs55
Copy link
Contributor

djs55 commented Apr 13, 2021

Hm if I comment out the doHotplug function then it works:

% docker run -it --privileged --pid=host justincormack/nsenter1
/ # ls -l /dev/tty
crw-rw-rw-    1 root     root        5,   0 Jan  1  1970 /dev/tty

@djs55
Copy link
Contributor

djs55 commented Apr 13, 2021

The permissions seem to change after running mdev -s. According to https://git.busybox.net/busybox/tree/docs/mdev.txt?h=1_18_stable

Mdev has an optional config file for controlling ownership/permissions of
device nodes if your system needs something more than the default root/root
660 permissions.

@dave-tucker
Copy link
Member

What's odd is that even the older bash style init script called mdev -s 🤔
Perhaps the behaviour has changed in an alpine version bump?

@dqminh
Copy link

dqminh commented Nov 15, 2021

Any progress on this ? @thaJeztah

The permissions seem to change after running mdev -s
What's odd is that even the older bash style init script called mdev -s

Are we sure that the behavior was correct with older bash style init ? It looks to me that mdev -s always mknod the devices it found with default permission 660 unless it finds any overrides (which we don't have).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants