diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2020-12-17 15:05:14 +0100 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2020-12-17 15:05:14 +0100 |
| commit | 4bc59a4cf73f1dd9326d150bf6654b643e740fe2 (patch) | |
| tree | 0ec7900f236995003b4ba19b3e19d1e3e74a0ca6 | |
| parent | cd48f071173c2b24ae49f2b6eb35a76734f2d86b (diff) | |
| download | busybox-w32-4bc59a4cf73f1dd9326d150bf6654b643e740fe2.tar.gz busybox-w32-4bc59a4cf73f1dd9326d150bf6654b643e740fe2.tar.bz2 busybox-w32-4bc59a4cf73f1dd9326d150bf6654b643e740fe2.zip | |
mount: fix a race when a free loop device is snatched under us by another mount.
function old new delta
set_loop 850 809 -41
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
| -rw-r--r-- | libbb/loop.c | 138 |
1 files changed, 73 insertions, 65 deletions
diff --git a/libbb/loop.c b/libbb/loop.c index 85b2724e5..153990998 100644 --- a/libbb/loop.c +++ b/libbb/loop.c | |||
| @@ -98,9 +98,7 @@ int FAST_FUNC get_free_loop(void) | |||
| 98 | 98 | ||
| 99 | /* Returns opened fd to the loop device, <0 on error. | 99 | /* Returns opened fd to the loop device, <0 on error. |
| 100 | * *device is loop device to use, or if *device==NULL finds a loop device to | 100 | * *device is loop device to use, or if *device==NULL finds a loop device to |
| 101 | * mount it on and sets *device to a strdup of that loop device name. This | 101 | * mount it on and sets *device to a strdup of that loop device name. |
| 102 | * search will re-use an existing loop device already bound to that | ||
| 103 | * file/offset if it finds one. | ||
| 104 | */ | 102 | */ |
| 105 | int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offset, | 103 | int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offset, |
| 106 | unsigned long long sizelimit, unsigned flags) | 104 | unsigned long long sizelimit, unsigned flags) |
| @@ -109,9 +107,7 @@ int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offse | |||
| 109 | char *try; | 107 | char *try; |
| 110 | bb_loop_info loopinfo; | 108 | bb_loop_info loopinfo; |
| 111 | struct stat statbuf; | 109 | struct stat statbuf; |
| 112 | int i, dfd, ffd, mode, rc; | 110 | int i, lfd, ffd, mode, rc; |
| 113 | |||
| 114 | rc = dfd = -1; | ||
| 115 | 111 | ||
| 116 | /* Open the file. Barf if this doesn't work. */ | 112 | /* Open the file. Barf if this doesn't work. */ |
| 117 | mode = (flags & BB_LO_FLAGS_READ_ONLY) ? O_RDONLY : O_RDWR; | 113 | mode = (flags & BB_LO_FLAGS_READ_ONLY) ? O_RDONLY : O_RDWR; |
| @@ -127,24 +123,23 @@ int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offse | |||
| 127 | 123 | ||
| 128 | try = *device; | 124 | try = *device; |
| 129 | if (!try) { | 125 | if (!try) { |
| 126 | get_free_loopN: | ||
| 130 | i = get_free_loop(); | 127 | i = get_free_loop(); |
| 131 | if (i == -2) { /* no /dev/loop-control */ | ||
| 132 | i = 0; | ||
| 133 | try = dev; | ||
| 134 | goto old_style; | ||
| 135 | } | ||
| 136 | if (i == -1) { | 128 | if (i == -1) { |
| 137 | close(ffd); | 129 | close(ffd); |
| 138 | return -1; /* no free loop devices */ | 130 | return -1; /* no free loop devices */ |
| 139 | } | 131 | } |
| 140 | try = *device = xasprintf(LOOP_FORMAT, i); | 132 | if (i >= 0) { |
| 141 | goto try_to_open; | 133 | try = xasprintf(LOOP_FORMAT, i); |
| 134 | goto open_lfd; | ||
| 135 | } | ||
| 136 | /* i == -2: no /dev/loop-control. Do an old-style search for a free device */ | ||
| 137 | try = dev; | ||
| 142 | } | 138 | } |
| 143 | 139 | ||
| 144 | old_style: | 140 | /* Find a loop device */ |
| 145 | /* Find a loop device. */ | 141 | /* 0xfffff is a max possible minor number in Linux circa 2010 */ |
| 146 | /* 1048575 (0xfffff) is a max possible minor number in Linux circa 2010 */ | 142 | for (i = 0; i <= 0xfffff; i++) { |
| 147 | for (i = 0; rc && i < 1048576; i++) { | ||
| 148 | sprintf(dev, LOOP_FORMAT, i); | 143 | sprintf(dev, LOOP_FORMAT, i); |
| 149 | 144 | ||
| 150 | IF_FEATURE_MOUNT_LOOP_CREATE(errno = 0;) | 145 | IF_FEATURE_MOUNT_LOOP_CREATE(errno = 0;) |
| @@ -153,72 +148,85 @@ int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offse | |||
| 153 | && errno == ENOENT | 148 | && errno == ENOENT |
| 154 | && try == dev | 149 | && try == dev |
| 155 | ) { | 150 | ) { |
| 156 | /* Node doesn't exist, try to create it. */ | 151 | /* Node doesn't exist, try to create it */ |
| 157 | if (mknod(dev, S_IFBLK|0644, makedev(7, i)) == 0) | 152 | if (mknod(dev, S_IFBLK|0644, makedev(7, i)) == 0) |
| 158 | goto try_to_open; | 153 | goto open_lfd; |
| 159 | } | 154 | } |
| 160 | /* Ran out of block devices, return failure. */ | 155 | /* Ran out of block devices, return failure */ |
| 161 | rc = -1; | 156 | rc = -1; |
| 162 | break; | 157 | break; |
| 163 | } | 158 | } |
| 164 | try_to_open: | 159 | open_lfd: |
| 165 | /* Open the sucker and check its loopiness. */ | 160 | /* Open the sucker and check its loopiness */ |
| 166 | dfd = open(try, mode); | 161 | lfd = rc = open(try, mode); |
| 167 | if (dfd < 0 && errno == EROFS) { | 162 | if (lfd < 0 && errno == EROFS) { |
| 168 | mode = O_RDONLY; | 163 | mode = O_RDONLY; |
| 169 | dfd = open(try, mode); | 164 | lfd = rc = open(try, mode); |
| 170 | } | 165 | } |
| 171 | if (dfd < 0) { | 166 | if (lfd < 0) { |
| 172 | if (errno == ENXIO) { | 167 | if (errno == ENXIO) { |
| 173 | /* Happens if loop module is not loaded */ | 168 | /* Happens if loop module is not loaded */ |
| 174 | rc = -1; | 169 | /* rc is -1; */ |
| 175 | break; | 170 | break; |
| 176 | } | 171 | } |
| 177 | goto try_again; | 172 | goto try_next_loopN; |
| 178 | } | 173 | } |
| 179 | 174 | ||
| 180 | rc = ioctl(dfd, BB_LOOP_GET_STATUS, &loopinfo); | 175 | rc = ioctl(lfd, BB_LOOP_GET_STATUS, &loopinfo); |
| 181 | 176 | ||
| 182 | /* If device is free, claim it. */ | 177 | /* If device is free, try to claim it */ |
| 183 | if (rc && errno == ENXIO) { | 178 | if (rc && errno == ENXIO) { |
| 184 | /* Associate free loop device with file. */ | 179 | /* Associate free loop device with file */ |
| 185 | if (ioctl(dfd, LOOP_SET_FD, ffd) == 0) { | 180 | if (ioctl(lfd, LOOP_SET_FD, ffd)) { |
| 186 | memset(&loopinfo, 0, sizeof(loopinfo)); | 181 | /* Ouch. Are we racing with other mount? */ |
| 187 | safe_strncpy((char *)loopinfo.lo_file_name, file, LO_NAME_SIZE); | 182 | if (!*device /* yes */ |
| 188 | loopinfo.lo_offset = offset; | 183 | && try != dev /* tried a _kernel-offered_ loopN? */ |
| 189 | loopinfo.lo_sizelimit = sizelimit; | 184 | ) { |
| 190 | /* | 185 | free(try); |
| 191 | * Used by mount to set LO_FLAGS_AUTOCLEAR. | 186 | close(lfd); |
| 192 | * LO_FLAGS_READ_ONLY is not set because RO is controlled by open type of the file. | 187 | //TODO: add "if (--failcount != 0) ..."? |
| 193 | * Note that closing LO_FLAGS_AUTOCLEARed dfd before mount | 188 | goto get_free_loopN; |
| 194 | * is wrong (would free the loop device!) | ||
| 195 | */ | ||
| 196 | loopinfo.lo_flags = (flags & ~BB_LO_FLAGS_READ_ONLY); | ||
| 197 | rc = ioctl(dfd, BB_LOOP_SET_STATUS, &loopinfo); | ||
| 198 | if (rc != 0 && (loopinfo.lo_flags & BB_LO_FLAGS_AUTOCLEAR)) { | ||
| 199 | /* Old kernel, does not support LO_FLAGS_AUTOCLEAR? */ | ||
| 200 | /* (this code path is not tested) */ | ||
| 201 | loopinfo.lo_flags -= BB_LO_FLAGS_AUTOCLEAR; | ||
| 202 | rc = ioctl(dfd, BB_LOOP_SET_STATUS, &loopinfo); | ||
| 203 | } | ||
| 204 | if (rc != 0) { | ||
| 205 | ioctl(dfd, LOOP_CLR_FD, 0); // actually, 0 param is unnecessary | ||
| 206 | } | 189 | } |
| 190 | goto try_next_loopN; | ||
| 207 | } | 191 | } |
| 208 | } else { | 192 | memset(&loopinfo, 0, sizeof(loopinfo)); |
| 209 | rc = -1; | 193 | safe_strncpy((char *)loopinfo.lo_file_name, file, LO_NAME_SIZE); |
| 210 | } | 194 | loopinfo.lo_offset = offset; |
| 211 | if (rc != 0) { | 195 | loopinfo.lo_sizelimit = sizelimit; |
| 212 | close(dfd); | 196 | /* |
| 197 | * Used by mount to set LO_FLAGS_AUTOCLEAR. | ||
| 198 | * LO_FLAGS_READ_ONLY is not set because RO is controlled by open type of the file. | ||
| 199 | * Note that closing LO_FLAGS_AUTOCLEARed lfd before mount | ||
| 200 | * is wrong (would free the loop device!) | ||
| 201 | */ | ||
| 202 | loopinfo.lo_flags = (flags & ~BB_LO_FLAGS_READ_ONLY); | ||
| 203 | rc = ioctl(lfd, BB_LOOP_SET_STATUS, &loopinfo); | ||
| 204 | if (rc != 0 && (loopinfo.lo_flags & BB_LO_FLAGS_AUTOCLEAR)) { | ||
| 205 | /* Old kernel, does not support LO_FLAGS_AUTOCLEAR? */ | ||
| 206 | /* (this code path is not tested) */ | ||
| 207 | loopinfo.lo_flags -= BB_LO_FLAGS_AUTOCLEAR; | ||
| 208 | rc = ioctl(lfd, BB_LOOP_SET_STATUS, &loopinfo); | ||
| 209 | } | ||
| 210 | if (rc == 0) { | ||
| 211 | /* SUCCESS! */ | ||
| 212 | if (try != dev) /* tried a kernel-offered free loopN? */ | ||
| 213 | *device = try; /* malloced */ | ||
| 214 | if (!*device) /* was looping in search of free "/dev/loopN"? */ | ||
| 215 | *device = xstrdup(dev); | ||
| 216 | rc = lfd; /* return this */ | ||
| 217 | break; | ||
| 218 | } | ||
| 219 | /* failure, undo LOOP_SET_FD */ | ||
| 220 | ioctl(lfd, LOOP_CLR_FD, 0); // actually, 0 param is unnecessary | ||
| 213 | } | 221 | } |
| 214 | try_again: | 222 | /* else: device is not free (rc == 0) or error other than ENXIO */ |
| 215 | if (*device) break; | 223 | close(lfd); |
| 216 | } | 224 | try_next_loopN: |
| 225 | rc = -1; | ||
| 226 | if (*device) /* was looking for a particular "/dev/loopN"? */ | ||
| 227 | break; /* yes, do not try other names */ | ||
| 228 | } /* for() */ | ||
| 229 | |||
| 217 | close(ffd); | 230 | close(ffd); |
| 218 | if (rc == 0) { | ||
| 219 | if (!*device) | ||
| 220 | *device = xstrdup(dev); | ||
| 221 | return dfd; | ||
| 222 | } | ||
| 223 | return rc; | 231 | return rc; |
| 224 | } | 232 | } |
