github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/layer/utils.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016, 2017, 2018 SUSE LLC. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package layer 19 20 import ( 21 "archive/tar" 22 "os" 23 "path/filepath" 24 25 "github.com/apex/log" 26 "github.com/golang/protobuf/proto" 27 "github.com/openSUSE/umoci/pkg/idtools" 28 rspec "github.com/opencontainers/runtime-spec/specs-go" 29 "github.com/pkg/errors" 30 rootlesscontainers "github.com/rootless-containers/proto/go-proto" 31 ) 32 33 // MapOptions specifies the UID and GID mappings used when unpacking and 34 // repacking images. 35 type MapOptions struct { 36 // UIDMappings and GIDMappings are the UID and GID mappings to apply when 37 // packing and unpacking image rootfs layers. 38 UIDMappings []rspec.LinuxIDMapping `json:"uid_mappings"` 39 GIDMappings []rspec.LinuxIDMapping `json:"gid_mappings"` 40 41 // Rootless specifies whether any to error out if chown fails. 42 Rootless bool `json:"rootless"` 43 44 // KeepDirlinks is essentially the same as rsync's optio 45 // --keep-dirlinks: if, on extraction, a directory would be created 46 // where a symlink to a directory previously existed, KeepDirlinks 47 // doesn't create that directory, but instead just uses the existing 48 // symlink. 49 KeepDirlinks bool `json:"-"` 50 } 51 52 // mapHeader maps a tar.Header generated from the filesystem so that it 53 // describes the inode as it would be observed by a container process. In 54 // particular this involves apply an ID mapping from the host filesystem to the 55 // container mappings. Returns an error if it's not possible to map the given 56 // UID. 57 func mapHeader(hdr *tar.Header, mapOptions MapOptions) error { 58 var newUID, newGID int 59 60 // It only makes sense to do un-mapping if we're not rootless. If we're 61 // rootless then all of the files will be owned by us anyway. 62 if !mapOptions.Rootless { 63 var err error 64 newUID, err = idtools.ToContainer(hdr.Uid, mapOptions.UIDMappings) 65 if err != nil { 66 return errors.Wrap(err, "map uid to container") 67 } 68 newGID, err = idtools.ToContainer(hdr.Gid, mapOptions.GIDMappings) 69 if err != nil { 70 return errors.Wrap(err, "map gid to container") 71 } 72 } 73 74 // We have special handling for the "user.rootlesscontainers" xattr. If 75 // we're rootless then we override the owner of the file we're currently 76 // parsing (and then remove the xattr). If we're not rootless then the user 77 // is doing something strange, so we log a warning but just ignore the 78 // xattr otherwise. 79 // 80 // TODO: We should probably add a flag to opt-out of this (though I'm not 81 // sure why anyone would intentionally use this incorrectly). 82 if value, ok := hdr.Xattrs[rootlesscontainers.Keyname]; !ok { 83 // noop 84 } else if !mapOptions.Rootless { 85 log.Warnf("suspicious filesystem: saw special rootless xattr %s in non-rootless invocation", rootlesscontainers.Keyname) 86 } else { 87 var payload rootlesscontainers.Resource 88 if err := proto.Unmarshal([]byte(value), &payload); err != nil { 89 return errors.Wrap(err, "unmarshal rootlesscontainers payload") 90 } 91 92 // If the payload isn't uint32(-1) we apply it. The xattr includes the 93 // *in-container* owner so we don't want to map it. 94 if uid := payload.GetUid(); uid != rootlesscontainers.NoopID { 95 newUID = int(uid) 96 } 97 if gid := payload.GetGid(); gid != rootlesscontainers.NoopID { 98 newGID = int(gid) 99 } 100 101 // Drop the xattr since it's just a marker for us and shouldn't be in 102 // layers. This is technically out-of-spec, but so is 103 // "user.rootlesscontainers". 104 delete(hdr.Xattrs, rootlesscontainers.Keyname) 105 } 106 107 hdr.Uid = newUID 108 hdr.Gid = newGID 109 return nil 110 } 111 112 // unmapHeader maps a tar.Header from a tar layer stream so that it describes 113 // the inode as it would be exist on the host filesystem. In particular this 114 // involves applying an ID mapping from the container filesystem to the host 115 // mappings. Returns an error if it's not possible to map the given UID. 116 func unmapHeader(hdr *tar.Header, mapOptions MapOptions) error { 117 // To avoid nil references. 118 if hdr.Xattrs == nil { 119 hdr.Xattrs = make(map[string]string) 120 } 121 122 // If there is already a "user.rootlesscontainers" we give a warning in 123 // both rootless and root cases -- but in rootless we explicitly delete the 124 // entry because we might replace it. 125 if _, ok := hdr.Xattrs[rootlesscontainers.Keyname]; ok { 126 if mapOptions.Rootless { 127 log.Warnf("rootless{%s} ignoring special xattr %s stored in layer", hdr.Name, rootlesscontainers.Keyname) 128 delete(hdr.Xattrs, rootlesscontainers.Keyname) 129 } else { 130 log.Warnf("suspicious layer: saw special xattr %s in non-rootless invocation", rootlesscontainers.Keyname) 131 } 132 } 133 134 // In rootless mode there are a few things we need to do. We need to map 135 // all of the files in the layer to have an owner of (0, 0) because we 136 // cannot lchown(2) anything -- and then if the owner was non-root we have 137 // to create a "user.rootlesscontainers" xattr for it. 138 if mapOptions.Rootless { 139 // Fill the rootlesscontainers payload with the original (uid, gid). If 140 // either is 0, we replace it with uint32(-1). Technically we could 141 // just leave it as 0 (since that is what the source of truth told us 142 // the owner was), but this would result in a massive increase in 143 // xattrs with no real benefit. 144 payload := rootlesscontainers.Resource{ 145 Uid: rootlesscontainers.NoopID, 146 Gid: rootlesscontainers.NoopID, 147 } 148 if uid := hdr.Uid; uid != 0 { 149 payload.Uid = uint32(uid) 150 } 151 if gid := hdr.Gid; gid != 0 { 152 payload.Gid = uint32(gid) 153 } 154 155 // Don't add the xattr if the owner isn't just (0, 0) because that's a 156 // waste of space. 157 if !rootlesscontainers.IsDefault(payload) { 158 valueBytes, err := proto.Marshal(&payload) 159 if err != nil { 160 return errors.Wrap(err, "marshal rootlesscontainers payload") 161 } 162 // While the payload is almost certainly not UTF-8, Go strings can 163 // actually be arbitrary bytes (in case you didn't know this and 164 // were confused like me when this worked). See 165 // <https://blog.golang.org/strings> for more detail. 166 hdr.Xattrs[rootlesscontainers.Keyname] = string(valueBytes) 167 } 168 169 hdr.Uid = 0 170 hdr.Gid = 0 171 } 172 173 newUID, err := idtools.ToHost(hdr.Uid, mapOptions.UIDMappings) 174 if err != nil { 175 return errors.Wrap(err, "map uid to host") 176 } 177 newGID, err := idtools.ToHost(hdr.Gid, mapOptions.GIDMappings) 178 if err != nil { 179 return errors.Wrap(err, "map gid to host") 180 } 181 182 hdr.Uid = newUID 183 hdr.Gid = newGID 184 return nil 185 } 186 187 // CleanPath makes a path safe for use with filepath.Join. This is done by not 188 // only cleaning the path, but also (if the path is relative) adding a leading 189 // '/' and cleaning it (then removing the leading '/'). This ensures that a 190 // path resulting from prepending another path will always resolve to lexically 191 // be a subdirectory of the prefixed path. This is all done lexically, so paths 192 // that include symlinks won't be safe as a result of using CleanPath. 193 // 194 // This function comes from runC (libcontainer/utils/utils.go). 195 func CleanPath(path string) string { 196 // Deal with empty strings nicely. 197 if path == "" { 198 return "" 199 } 200 201 // Ensure that all paths are cleaned (especially problematic ones like 202 // "/../../../../../" which can cause lots of issues). 203 path = filepath.Clean(path) 204 205 // If the path isn't absolute, we need to do more processing to fix paths 206 // such as "../../../../<etc>/some/path". We also shouldn't convert absolute 207 // paths to relative ones. 208 if !filepath.IsAbs(path) { 209 path = filepath.Clean(string(os.PathSeparator) + path) 210 // This can't fail, as (by definition) all paths are relative to root. 211 path, _ = filepath.Rel(string(os.PathSeparator), path) 212 } 213 214 // Clean the path again for good measure. 215 return filepath.Clean(path) 216 } 217 218 // InnerErrno returns the "real" system error from an error that originally 219 // came from the "os" package. The returned error can be compared directly with 220 // unix.* (or syscall.*) errno values. If the type could not be detected we just return 221 func InnerErrno(err error) error { 222 // All of the os.* cases as well as an explicit 223 errno := errors.Cause(err) 224 switch err := errno.(type) { 225 case *os.PathError: 226 errno = err.Err 227 case *os.LinkError: 228 errno = err.Err 229 case *os.SyscallError: 230 errno = err.Err 231 } 232 return errno 233 }