4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 #include <nbdkit-filter.h>
54 * A single struct range stores a range from start-end (inclusive).
55 * end can be INT64_MAX to indicate the end of the file.
57 * 'range_list' stores the list of protected ranges, unsorted.
59 struct range
{ uint64_t start
, end
; const char *description
; };
60 DEFINE_VECTOR_TYPE (ranges
, struct range
);
61 static ranges range_list
;
63 /* region_list covers the whole address space with protected and
66 static regions region_list
;
71 free (region_list
.ptr
);
72 free (range_list
.ptr
);
75 /* Parse "START-END" into a range or "~START-END" into two ranges.
76 * Adds the range(s) to range_list, or exits with an error.
79 parse_range (const char *value
)
81 CLEANUP_FREE
char *start_str
= NULL
;
85 const char *description
= value
;
87 if (value
[0] == '~') {
92 end_str
= strchr (value
, '-');
93 if (end_str
== NULL
) {
94 nbdkit_error ("cannot parse range, missing '-': %s", description
);
97 start_str
= strndup (value
, end_str
- value
);
98 if (start_str
== NULL
) {
99 nbdkit_error ("strndup: %m");
104 if (start_str
[0] == '\0')
106 else if (nbdkit_parse_uint64_t ("range", start_str
, &start
) == -1)
109 if (end_str
[0] == '\0')
111 else if (nbdkit_parse_uint64_t ("range", end_str
, &end
) == -1)
115 nbdkit_error ("invalid range, end < start: %s", description
);
121 { .start
= start
, .end
= end
, .description
= description
};
123 if (ranges_append (&range_list
, range
) == -1) {
124 nbdkit_error ("ranges_append: %m");
128 else { /* ~START-END creates up to two ranges */
129 struct range range
= { .description
= description
};
133 range
.end
= start
- 1;
134 if (ranges_append (&range_list
, range
) == -1) {
135 nbdkit_error ("ranges_append: %m");
139 if (end
< INT64_MAX
) {
140 range
.start
= end
+ 1;
141 range
.end
= INT64_MAX
;
142 if (ranges_append (&range_list
, range
) == -1) {
143 nbdkit_error ("ranges_append: %m");
151 protect_config (nbdkit_next_config
*next
, nbdkit_backend
*nxdata
,
152 const char *key
, const char *value
)
154 if (strcmp (key
, "protect") == 0) {
159 return next (nxdata
, key
, value
);
163 compare_ranges (const struct range
*r1
, const struct range
*r2
)
165 if (r1
->start
< r2
->start
)
167 else if (r1
->start
> r2
->start
)
174 append_unprotected_region (uint64_t end
)
176 if (append_region_end (®ion_list
, "unprotected", end
,
177 0, 0, region_data
, NULL
) == -1) {
178 nbdkit_error ("append region: %m");
184 append_protected_region (struct range range
)
186 assert (virtual_size (®ion_list
) == range
.start
);
188 if (append_region_end (®ion_list
, range
.description
, range
.end
,
189 0, 0, region_data
, "protected") == -1) {
190 nbdkit_error ("append region: %m");
196 protect_config_complete (nbdkit_next_config_complete
*next
,
197 nbdkit_backend
*nxdata
)
201 if (range_list
.len
> 0) {
202 /* Order the ranges and combine adjacent and overlapping ranges. */
203 ranges_sort (&range_list
, compare_ranges
);
205 /* Combine adjacent and overlapping ranges. */
206 for (i
= 0; i
< range_list
.len
- 1; ++i
) {
207 /* This is true because we've sorted the ranges. */
208 assert (range_list
.ptr
[i
].start
<= range_list
.ptr
[i
+1].start
);
210 /* Adjacent or overlapping with the next range? */
211 if (range_list
.ptr
[i
].end
+ 1 >= range_list
.ptr
[i
+1].start
) {
212 range_list
.ptr
[i
].end
= range_list
.ptr
[i
+1].end
;
213 ranges_remove (&range_list
, i
+1);
220 /* Now convert these to a complete list of regions covering the
221 * whole 64 bit address space.
223 * Insert an initial unprotected region before the first protected
226 if (range_list
.len
!= 0 && range_list
.ptr
[0].start
> 0)
227 append_unprotected_region (range_list
.ptr
[0].start
- 1);
229 for (i
= 0; i
< range_list
.len
; ++i
) {
230 append_protected_region (range_list
.ptr
[i
]);
232 /* Insert an unprotected region before the next range. */
233 if (i
+1 < range_list
.len
)
234 append_unprotected_region (range_list
.ptr
[i
+1].start
- 1);
237 /* Insert a final unprotected region at the end. */
238 if ((uint64_t) virtual_size (®ion_list
) < (uint64_t) INT64_MAX
)
239 append_unprotected_region (INT64_MAX
);
241 return next (nxdata
);
244 #define protect_config_help \
245 "protect=<START>-<END> Protect range of bytes START-END (inclusive)."
247 /* -D protect.write=1 to debug write checks. */
248 NBDKIT_DLL_PUBLIC
int protect_debug_write
= 0;
250 /* Check the proposed write operation.
252 * If offset and count contain any protected ranges, then we check
253 * that the write does not modify those ranges. If buf != NULL then
254 * we check that the data proposed to be written to the protected
255 * ranges matches what we read from the plugin. If buf == NULL then
256 * we check that the plugin reads zero for the protected ranges.
259 check_write (nbdkit_next
*next
,
260 uint32_t count
, uint64_t offset
, const void *buf
, int *err
)
263 const struct region
*region
;
268 region
= find_region (®ion_list
, offset
);
269 assert (region
!= NULL
);
270 assert (region
->type
== region_data
);
272 protected = region
->u
.data
!= NULL
;
273 len
= MIN (region
->end
- offset
+ 1, (uint64_t) count
);
276 if (protect_debug_write
)
277 nbdkit_debug ("protect: %s offset %" PRIu64
" length %" PRIu64
,
278 protected ? "checking protected region"
279 : "skipping unprotected region",
284 CLEANUP_FREE
char *expected
= malloc (len
);
285 if (expected
== NULL
) {
286 nbdkit_error ("malloc: %m");
291 /* Read the underlying plugin. */
292 r
= next
->pread (next
, expected
, len
, offset
, 0, err
);
294 return -1; /* read error */
296 /* Expected data should match buffer (or zero if buf == NULL). */
298 matches
= memcmp (expected
, buf
, len
) == 0;
300 matches
= is_zero (expected
, len
);
302 nbdkit_error ("protect filter prevented write to protected range %s",
303 region
->description
);
319 protect_pwrite (nbdkit_next
*next
,
322 uint32_t count
, uint64_t offset
, uint32_t flags
,
325 if (check_write (next
, count
, offset
, buf
, err
) == -1)
328 return next
->pwrite (next
, buf
, count
, offset
, flags
, err
);
333 protect_trim (nbdkit_next
*next
,
334 void *handle
, uint32_t count
, uint64_t offset
, uint32_t flags
,
337 if (check_write (next
, count
, offset
, NULL
, err
) == -1)
340 return next
->trim (next
, count
, offset
, flags
, err
);
345 protect_zero (nbdkit_next
*next
,
346 void *handle
, uint32_t count
, uint64_t offset
, uint32_t flags
,
349 if (check_write (next
, count
, offset
, NULL
, err
) == -1)
352 return next
->zero (next
, count
, offset
, flags
, err
);
355 static struct nbdkit_filter filter
= {
357 .longname
= "nbdkit protect filter",
358 .unload
= protect_unload
,
359 .config
= protect_config
,
360 .config_complete
= protect_config_complete
,
361 .config_help
= protect_config_help
,
362 .pwrite
= protect_pwrite
,
363 .trim
= protect_trim
,
364 .zero
= protect_zero
,
367 NBDKIT_REGISTER_FILTER (filter
)