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 | } |