This fix tries to address the issue raised in #22420. When
`--tmpfs` is specified with `/tmp`, the default value is
`rw,nosuid,nodev,noexec,relatime,size=65536k`. When `--tmpfs`
is specified with `/tmp:rw`, then the value changed to
`rw,nosuid,nodev,noexec,relatime`.
The reason for such an inconsistency is because docker tries
to add `size=65536k` option only when user provides no option.
This fix tries to address this issue by always pre-progating
`size=65536k` along with `rw,nosuid,nodev,noexec,relatime`.
If user provides a different value (e.g., `size=8192k`), it
will override the `size=65536k` anyway since the combined
options will be parsed and merged to remove any duplicates.
Additional test cases have been added to cover the changes
in this fix.
This fix fixes #22420.
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
| ... | ... |
@@ -480,14 +480,17 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c |
| 480 | 480 |
} |
| 481 | 481 |
|
| 482 | 482 |
if m.Source == "tmpfs" {
|
| 483 |
- opt := []string{"noexec", "nosuid", "nodev", volume.DefaultPropagationMode}
|
|
| 483 |
+ options := []string{"noexec", "nosuid", "nodev", volume.DefaultPropagationMode, "size=65536k"}
|
|
| 484 | 484 |
if m.Data != "" {
|
| 485 |
- opt = append(opt, strings.Split(m.Data, ",")...) |
|
| 486 |
- } else {
|
|
| 487 |
- opt = append(opt, "size=65536k") |
|
| 485 |
+ options = append(options, strings.Split(m.Data, ",")...) |
|
| 488 | 486 |
} |
| 489 | 487 |
|
| 490 |
- s.Mounts = append(s.Mounts, specs.Mount{Destination: m.Destination, Source: m.Source, Type: "tmpfs", Options: opt})
|
|
| 488 |
+ merged, err := mount.MergeTmpfsOptions(options) |
|
| 489 |
+ if err != nil {
|
|
| 490 |
+ return err |
|
| 491 |
+ } |
|
| 492 |
+ |
|
| 493 |
+ s.Mounts = append(s.Mounts, specs.Mount{Destination: m.Destination, Source: m.Source, Type: "tmpfs", Options: merged})
|
|
| 491 | 494 |
continue |
| 492 | 495 |
} |
| 493 | 496 |
|
| ... | ... |
@@ -824,6 +824,45 @@ func (s *DockerSuite) TestRunTmpfsMounts(c *check.C) {
|
| 824 | 824 |
} |
| 825 | 825 |
} |
| 826 | 826 |
|
| 827 |
+// Test case for #22420 |
|
| 828 |
+func (s *DockerSuite) TestRunTmpfsMountsWithOptions(c *check.C) {
|
|
| 829 |
+ testRequires(c, DaemonIsLinux) |
|
| 830 |
+ |
|
| 831 |
+ expectedOptions := []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=65536k"}
|
|
| 832 |
+ out, _ := dockerCmd(c, "run", "--tmpfs", "/tmp", "busybox", "sh", "-c", "mount | grep 'tmpfs on /tmp'") |
|
| 833 |
+ for _, option := range expectedOptions {
|
|
| 834 |
+ c.Assert(out, checker.Contains, option) |
|
| 835 |
+ } |
|
| 836 |
+ |
|
| 837 |
+ expectedOptions = []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=65536k"}
|
|
| 838 |
+ out, _ = dockerCmd(c, "run", "--tmpfs", "/tmp:rw", "busybox", "sh", "-c", "mount | grep 'tmpfs on /tmp'") |
|
| 839 |
+ for _, option := range expectedOptions {
|
|
| 840 |
+ c.Assert(out, checker.Contains, option) |
|
| 841 |
+ } |
|
| 842 |
+ |
|
| 843 |
+ expectedOptions = []string{"rw", "nosuid", "nodev", "relatime", "size=8192k"}
|
|
| 844 |
+ out, _ = dockerCmd(c, "run", "--tmpfs", "/tmp:rw,exec,size=8192k", "busybox", "sh", "-c", "mount | grep 'tmpfs on /tmp'") |
|
| 845 |
+ for _, option := range expectedOptions {
|
|
| 846 |
+ c.Assert(out, checker.Contains, option) |
|
| 847 |
+ } |
|
| 848 |
+ |
|
| 849 |
+ expectedOptions = []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k"}
|
|
| 850 |
+ out, _ = dockerCmd(c, "run", "--tmpfs", "/tmp:rw,size=8192k,exec,size=4096k,noexec", "busybox", "sh", "-c", "mount | grep 'tmpfs on /tmp'") |
|
| 851 |
+ for _, option := range expectedOptions {
|
|
| 852 |
+ c.Assert(out, checker.Contains, option) |
|
| 853 |
+ } |
|
| 854 |
+ |
|
| 855 |
+ // We use debian:jessie as there is no findmnt in busybox. Also the output will be in the format of |
|
| 856 |
+ // TARGET PROPAGATION |
|
| 857 |
+ // /tmp shared |
|
| 858 |
+ // so we only capture `shared` here. |
|
| 859 |
+ expectedOptions = []string{"shared"}
|
|
| 860 |
+ out, _ = dockerCmd(c, "run", "--tmpfs", "/tmp:shared", "debian:jessie", "findmnt", "-o", "TARGET,PROPAGATION", "/tmp") |
|
| 861 |
+ for _, option := range expectedOptions {
|
|
| 862 |
+ c.Assert(out, checker.Contains, option) |
|
| 863 |
+ } |
|
| 864 |
+} |
|
| 865 |
+ |
|
| 827 | 866 |
func (s *DockerSuite) TestRunSysctls(c *check.C) {
|
| 828 | 867 |
|
| 829 | 868 |
testRequires(c, DaemonIsLinux) |
| ... | ... |
@@ -5,6 +5,112 @@ import ( |
| 5 | 5 |
"strings" |
| 6 | 6 |
) |
| 7 | 7 |
|
| 8 |
+var flags = map[string]struct {
|
|
| 9 |
+ clear bool |
|
| 10 |
+ flag int |
|
| 11 |
+}{
|
|
| 12 |
+ "defaults": {false, 0},
|
|
| 13 |
+ "ro": {false, RDONLY},
|
|
| 14 |
+ "rw": {true, RDONLY},
|
|
| 15 |
+ "suid": {true, NOSUID},
|
|
| 16 |
+ "nosuid": {false, NOSUID},
|
|
| 17 |
+ "dev": {true, NODEV},
|
|
| 18 |
+ "nodev": {false, NODEV},
|
|
| 19 |
+ "exec": {true, NOEXEC},
|
|
| 20 |
+ "noexec": {false, NOEXEC},
|
|
| 21 |
+ "sync": {false, SYNCHRONOUS},
|
|
| 22 |
+ "async": {true, SYNCHRONOUS},
|
|
| 23 |
+ "dirsync": {false, DIRSYNC},
|
|
| 24 |
+ "remount": {false, REMOUNT},
|
|
| 25 |
+ "mand": {false, MANDLOCK},
|
|
| 26 |
+ "nomand": {true, MANDLOCK},
|
|
| 27 |
+ "atime": {true, NOATIME},
|
|
| 28 |
+ "noatime": {false, NOATIME},
|
|
| 29 |
+ "diratime": {true, NODIRATIME},
|
|
| 30 |
+ "nodiratime": {false, NODIRATIME},
|
|
| 31 |
+ "bind": {false, BIND},
|
|
| 32 |
+ "rbind": {false, RBIND},
|
|
| 33 |
+ "unbindable": {false, UNBINDABLE},
|
|
| 34 |
+ "runbindable": {false, RUNBINDABLE},
|
|
| 35 |
+ "private": {false, PRIVATE},
|
|
| 36 |
+ "rprivate": {false, RPRIVATE},
|
|
| 37 |
+ "shared": {false, SHARED},
|
|
| 38 |
+ "rshared": {false, RSHARED},
|
|
| 39 |
+ "slave": {false, SLAVE},
|
|
| 40 |
+ "rslave": {false, RSLAVE},
|
|
| 41 |
+ "relatime": {false, RELATIME},
|
|
| 42 |
+ "norelatime": {true, RELATIME},
|
|
| 43 |
+ "strictatime": {false, STRICTATIME},
|
|
| 44 |
+ "nostrictatime": {true, STRICTATIME},
|
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+var validFlags = map[string]bool{
|
|
| 48 |
+ "": true, |
|
| 49 |
+ "size": true, |
|
| 50 |
+ "mode": true, |
|
| 51 |
+ "uid": true, |
|
| 52 |
+ "gid": true, |
|
| 53 |
+ "nr_inodes": true, |
|
| 54 |
+ "nr_blocks": true, |
|
| 55 |
+ "mpol": true, |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+var propagationFlags = map[string]bool{
|
|
| 59 |
+ "bind": true, |
|
| 60 |
+ "rbind": true, |
|
| 61 |
+ "unbindable": true, |
|
| 62 |
+ "runbindable": true, |
|
| 63 |
+ "private": true, |
|
| 64 |
+ "rprivate": true, |
|
| 65 |
+ "shared": true, |
|
| 66 |
+ "rshared": true, |
|
| 67 |
+ "slave": true, |
|
| 68 |
+ "rslave": true, |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+// MergeTmpfsOptions merge mount options to make sure there is no duplicate. |
|
| 72 |
+func MergeTmpfsOptions(options []string) ([]string, error) {
|
|
| 73 |
+ // We use collisions maps to remove duplicates. |
|
| 74 |
+ // For flag, the key is the flag value (the key for propagation flag is -1) |
|
| 75 |
+ // For data=value, the key is the data |
|
| 76 |
+ flagCollisions := map[int]bool{}
|
|
| 77 |
+ dataCollisions := map[string]bool{}
|
|
| 78 |
+ |
|
| 79 |
+ var newOptions []string |
|
| 80 |
+ // We process in reverse order |
|
| 81 |
+ for i := len(options) - 1; i >= 0; i-- {
|
|
| 82 |
+ option := options[i] |
|
| 83 |
+ if option == "defaults" {
|
|
| 84 |
+ continue |
|
| 85 |
+ } |
|
| 86 |
+ if f, ok := flags[option]; ok && f.flag != 0 {
|
|
| 87 |
+ // There is only one propagation mode |
|
| 88 |
+ key := f.flag |
|
| 89 |
+ if propagationFlags[option] {
|
|
| 90 |
+ key = -1 |
|
| 91 |
+ } |
|
| 92 |
+ // Check to see if there is collision for flag |
|
| 93 |
+ if !flagCollisions[key] {
|
|
| 94 |
+ // We prepend the option and add to collision map |
|
| 95 |
+ newOptions = append([]string{option}, newOptions...)
|
|
| 96 |
+ flagCollisions[key] = true |
|
| 97 |
+ } |
|
| 98 |
+ continue |
|
| 99 |
+ } |
|
| 100 |
+ opt := strings.SplitN(option, "=", 2) |
|
| 101 |
+ if len(opt) != 2 || !validFlags[opt[0]] {
|
|
| 102 |
+ return nil, fmt.Errorf("Invalid tmpfs option %q", opt)
|
|
| 103 |
+ } |
|
| 104 |
+ if !dataCollisions[opt[0]] {
|
|
| 105 |
+ // We prepend the option and add to collision map |
|
| 106 |
+ newOptions = append([]string{option}, newOptions...)
|
|
| 107 |
+ dataCollisions[opt[0]] = true |
|
| 108 |
+ } |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 111 |
+ return newOptions, nil |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 8 | 114 |
// Parse fstab type mount options into mount() flags |
| 9 | 115 |
// and device specific data |
| 10 | 116 |
func parseOptions(options string) (int, string) {
|
| ... | ... |
@@ -13,45 +119,6 @@ func parseOptions(options string) (int, string) {
|
| 13 | 13 |
data []string |
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 |
- flags := map[string]struct {
|
|
| 17 |
- clear bool |
|
| 18 |
- flag int |
|
| 19 |
- }{
|
|
| 20 |
- "defaults": {false, 0},
|
|
| 21 |
- "ro": {false, RDONLY},
|
|
| 22 |
- "rw": {true, RDONLY},
|
|
| 23 |
- "suid": {true, NOSUID},
|
|
| 24 |
- "nosuid": {false, NOSUID},
|
|
| 25 |
- "dev": {true, NODEV},
|
|
| 26 |
- "nodev": {false, NODEV},
|
|
| 27 |
- "exec": {true, NOEXEC},
|
|
| 28 |
- "noexec": {false, NOEXEC},
|
|
| 29 |
- "sync": {false, SYNCHRONOUS},
|
|
| 30 |
- "async": {true, SYNCHRONOUS},
|
|
| 31 |
- "dirsync": {false, DIRSYNC},
|
|
| 32 |
- "remount": {false, REMOUNT},
|
|
| 33 |
- "mand": {false, MANDLOCK},
|
|
| 34 |
- "nomand": {true, MANDLOCK},
|
|
| 35 |
- "atime": {true, NOATIME},
|
|
| 36 |
- "noatime": {false, NOATIME},
|
|
| 37 |
- "diratime": {true, NODIRATIME},
|
|
| 38 |
- "nodiratime": {false, NODIRATIME},
|
|
| 39 |
- "bind": {false, BIND},
|
|
| 40 |
- "rbind": {false, RBIND},
|
|
| 41 |
- "unbindable": {false, UNBINDABLE},
|
|
| 42 |
- "runbindable": {false, RUNBINDABLE},
|
|
| 43 |
- "private": {false, PRIVATE},
|
|
| 44 |
- "rprivate": {false, RPRIVATE},
|
|
| 45 |
- "shared": {false, SHARED},
|
|
| 46 |
- "rshared": {false, RSHARED},
|
|
| 47 |
- "slave": {false, SLAVE},
|
|
| 48 |
- "rslave": {false, RSLAVE},
|
|
| 49 |
- "relatime": {false, RELATIME},
|
|
| 50 |
- "norelatime": {true, RELATIME},
|
|
| 51 |
- "strictatime": {false, STRICTATIME},
|
|
| 52 |
- "nostrictatime": {true, STRICTATIME},
|
|
| 53 |
- } |
|
| 54 |
- |
|
| 55 | 16 |
for _, o := range strings.Split(options, ",") {
|
| 56 | 17 |
// If the option does not exist in the flags table or the flag |
| 57 | 18 |
// is not supported on the platform, |
| ... | ... |
@@ -72,16 +139,6 @@ func parseOptions(options string) (int, string) {
|
| 72 | 72 |
// ParseTmpfsOptions parse fstab type mount options into flags and data |
| 73 | 73 |
func ParseTmpfsOptions(options string) (int, string, error) {
|
| 74 | 74 |
flags, data := parseOptions(options) |
| 75 |
- validFlags := map[string]bool{
|
|
| 76 |
- "": true, |
|
| 77 |
- "size": true, |
|
| 78 |
- "mode": true, |
|
| 79 |
- "uid": true, |
|
| 80 |
- "gid": true, |
|
| 81 |
- "nr_inodes": true, |
|
| 82 |
- "nr_blocks": true, |
|
| 83 |
- "mpol": true, |
|
| 84 |
- } |
|
| 85 | 75 |
for _, o := range strings.Split(data, ",") {
|
| 86 | 76 |
opt := strings.SplitN(o, "=", 2) |
| 87 | 77 |
if !validFlags[opt[0]] {
|
| ... | ... |
@@ -137,3 +137,26 @@ func TestGetMounts(t *testing.T) {
|
| 137 | 137 |
t.Fatal("/ should be mounted at least")
|
| 138 | 138 |
} |
| 139 | 139 |
} |
| 140 |
+ |
|
| 141 |
+func TestMergeTmpfsOptions(t *testing.T) {
|
|
| 142 |
+ options := []string{"noatime", "ro", "size=10k", "defaults", "atime", "defaults", "rw", "rprivate", "size=1024k", "slave"}
|
|
| 143 |
+ expected := []string{"atime", "rw", "size=1024k", "slave"}
|
|
| 144 |
+ merged, err := MergeTmpfsOptions(options) |
|
| 145 |
+ if err != nil {
|
|
| 146 |
+ t.Fatal(err) |
|
| 147 |
+ } |
|
| 148 |
+ if len(expected) != len(merged) {
|
|
| 149 |
+ t.Fatalf("Expected %s got %s", expected, merged)
|
|
| 150 |
+ } |
|
| 151 |
+ for index := range merged {
|
|
| 152 |
+ if merged[index] != expected[index] {
|
|
| 153 |
+ t.Fatalf("Expected %s for the %dth option, got %s", expected, index, merged)
|
|
| 154 |
+ } |
|
| 155 |
+ } |
|
| 156 |
+ |
|
| 157 |
+ options = []string{"noatime", "ro", "size=10k", "atime", "rw", "rprivate", "size=1024k", "slave", "size"}
|
|
| 158 |
+ _, err = MergeTmpfsOptions(options) |
|
| 159 |
+ if err == nil {
|
|
| 160 |
+ t.Fatal("Expected error got nil")
|
|
| 161 |
+ } |
|
| 162 |
+} |