Browse Source

[iobuf] Improve robustness of I/O buffer allocation

Guard against various corner cases (such as zero-length buffers, zero
alignments, and integer overflow when rounding up allocation lengths
and alignments) and ensure that the struct io_buffer is correctly
aligned even when the caller requests a non-zero alignment for the I/O
buffer itself.

Add self-tests to verify that the resulting alignments and lengths are
correct for a range of allocations.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown 8 years ago
parent
commit
12b3b57886
3 changed files with 171 additions and 9 deletions
  1. 34
    9
      src/core/iobuf.c
  2. 136
    0
      src/tests/iobuf_test.c
  3. 1
    0
      src/tests/tests.c

+ 34
- 9
src/core/iobuf.c View File

@@ -47,20 +47,45 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
47 47
  */
48 48
 struct io_buffer * alloc_iob_raw ( size_t len, size_t align, size_t offset ) {
49 49
 	struct io_buffer *iobuf;
50
+	size_t padding;
51
+	size_t threshold;
52
+	unsigned int align_log2;
50 53
 	void *data;
51 54
 
52
-	/* Align buffer length to ensure that struct io_buffer is aligned */
53
-	len = ( len + __alignof__ ( *iobuf ) - 1 ) &
54
-		~( __alignof__ ( *iobuf ) - 1 );
55
+	/* Calculate padding required below alignment boundary to
56
+	 * ensure that a correctly aligned inline struct io_buffer
57
+	 * could fit (regardless of the requested offset).
58
+	 */
59
+	padding = ( sizeof ( *iobuf ) + __alignof__ ( *iobuf ) - 1 );
55 60
 
56
-	/* Round up alignment to the nearest power of two */
57
-	align = ( 1 << fls ( align - 1 ) );
61
+	/* Round up requested alignment to at least the size of the
62
+	 * padding, to simplify subsequent calculations.
63
+	 */
64
+	if ( align < padding )
65
+		align = padding;
58 66
 
59
-	/* Allocate buffer plus descriptor as a single unit, unless
60
-	 * doing so will push the total size over the alignment
61
-	 * boundary.
67
+	/* Round up alignment to the nearest power of two, avoiding
68
+	 * a potentially undefined shift operation.
62 69
 	 */
63
-	if ( ( len + sizeof ( *iobuf ) ) <= align ) {
70
+	align_log2 = fls ( align - 1 );
71
+	if ( align_log2 >= ( 8 * sizeof ( align ) ) )
72
+		return NULL;
73
+	align = ( 1UL << align_log2 );
74
+
75
+	/* Calculate length threshold */
76
+	assert ( align >= padding );
77
+	threshold = ( align - padding );
78
+
79
+	/* Allocate buffer plus an inline descriptor as a single unit,
80
+	 * unless doing so would push the total size over the
81
+	 * alignment boundary.
82
+	 */
83
+	if ( len <= threshold ) {
84
+
85
+		/* Round up buffer length to ensure that struct
86
+		 * io_buffer is aligned.
87
+		 */
88
+		len += ( ( - len - offset ) & ( __alignof__ ( *iobuf ) - 1 ) );
64 89
 
65 90
 		/* Allocate memory for buffer plus descriptor */
66 91
 		data = malloc_dma_offset ( len + sizeof ( *iobuf ), align,

+ 136
- 0
src/tests/iobuf_test.c View File

@@ -0,0 +1,136 @@
1
+/*
2
+ * Copyright (C) 2016 Michael Brown <mbrown@fensystems.co.uk>.
3
+ *
4
+ * This program is free software; you can redistribute it and/or
5
+ * modify it under the terms of the GNU General Public License as
6
+ * published by the Free Software Foundation; either version 2 of the
7
+ * License, or (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful, but
10
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
+ * General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program; if not, write to the Free Software
16
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17
+ * 02110-1301, USA.
18
+ *
19
+ * You can also choose to distribute this program under the terms of
20
+ * the Unmodified Binary Distribution Licence (as given in the file
21
+ * COPYING.UBDL), provided that you have satisfied its requirements.
22
+ */
23
+
24
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
+
26
+/** @file
27
+ *
28
+ * I/O buffer tests
29
+ *
30
+ */
31
+
32
+/* Forcibly enable assertions */
33
+#undef NDEBUG
34
+
35
+#include <stdint.h>
36
+#include <string.h>
37
+#include <assert.h>
38
+#include <ipxe/iobuf.h>
39
+#include <ipxe/io.h>
40
+#include <ipxe/test.h>
41
+
42
+/* Forward declaration */
43
+struct self_test iobuf_test __self_test;
44
+
45
+/**
46
+ * Report I/O buffer allocation test result
47
+ *
48
+ * @v len		Required length of buffer
49
+ * @v align		Physical alignment
50
+ * @v offset		Offset from physical alignment
51
+ * @v file		Test code file
52
+ * @v line		Test code line
53
+ */
54
+static inline void alloc_iob_okx ( size_t len, size_t align, size_t offset,
55
+				   const char *file, unsigned int line ) {
56
+	struct io_buffer *iobuf;
57
+
58
+	/* Allocate I/O buffer */
59
+	iobuf = alloc_iob_raw ( len, align, offset );
60
+	okx ( iobuf != NULL, file, line );
61
+	DBGC ( &iobuf_test, "IOBUF %p (%#08lx+%#zx) for %#zx align %#zx "
62
+	       "offset %#zx\n", iobuf, virt_to_phys ( iobuf->data ),
63
+	       iob_tailroom ( iobuf ), len, align, offset );
64
+
65
+	/* Validate requested length and alignment */
66
+	okx ( ( ( ( intptr_t ) iobuf ) & ( __alignof__ ( *iobuf ) - 1 ) ) == 0,
67
+		file, line );
68
+	okx ( iob_tailroom ( iobuf ) >= len, file, line );
69
+	okx ( ( ( align == 0 ) ||
70
+		( ( virt_to_phys ( iobuf->data ) & ( align - 1 ) ) ==
71
+		  ( offset & ( align - 1 ) ) ) ), file, line );
72
+
73
+	/* Overwrite entire content of I/O buffer (for Valgrind) */
74
+	memset ( iob_put ( iobuf, len ), 0x55, len );
75
+
76
+	/* Free I/O buffer */
77
+	free_iob ( iobuf );
78
+}
79
+#define alloc_iob_ok( len, align, offset ) \
80
+	alloc_iob_okx ( len, align, offset, __FILE__, __LINE__ )
81
+
82
+/**
83
+ * Report I/O buffer allocation failure test result
84
+ *
85
+ * @v len		Required length of buffer
86
+ * @v align		Physical alignment
87
+ * @v offset		Offset from physical alignment
88
+ * @v file		Test code file
89
+ * @v line		Test code line
90
+ */
91
+static inline void alloc_iob_fail_okx ( size_t len, size_t align, size_t offset,
92
+					const char *file, unsigned int line ) {
93
+	struct io_buffer *iobuf;
94
+
95
+	/* Allocate I/O buffer */
96
+	iobuf = alloc_iob_raw ( len, align, offset );
97
+	okx ( iobuf == NULL, file, line );
98
+}
99
+#define alloc_iob_fail_ok( len, align, offset ) \
100
+	alloc_iob_fail_okx ( len, align, offset, __FILE__, __LINE__ )
101
+
102
+/**
103
+ * Perform I/O buffer self-tests
104
+ *
105
+ */
106
+static void iobuf_test_exec ( void ) {
107
+
108
+	/* Check zero-length allocations */
109
+	alloc_iob_ok ( 0, 0, 0 );
110
+	alloc_iob_ok ( 0, 0, 1 );
111
+	alloc_iob_ok ( 0, 1, 0 );
112
+	alloc_iob_ok ( 0, 1024, 0 );
113
+	alloc_iob_ok ( 0, 139, -17 );
114
+
115
+	/* Check various sensible allocations */
116
+	alloc_iob_ok ( 1, 0, 0 );
117
+	alloc_iob_ok ( 16, 16, 0 );
118
+	alloc_iob_ok ( 64, 0, 0 );
119
+	alloc_iob_ok ( 65, 0, 0 );
120
+	alloc_iob_ok ( 65, 1024, 19 );
121
+	alloc_iob_ok ( 1536, 1536, 0 );
122
+	alloc_iob_ok ( 2048, 2048, 0 );
123
+	alloc_iob_ok ( 2048, 2048, -10 );
124
+
125
+	/* Excessively large or excessively aligned allocations should fail */
126
+	alloc_iob_fail_ok ( -1UL, 0, 0 );
127
+	alloc_iob_fail_ok ( -1UL, 1024, 0 );
128
+	alloc_iob_fail_ok ( 0, -1UL, 0 );
129
+	alloc_iob_fail_ok ( 1024, -1UL, 0 );
130
+}
131
+
132
+/** I/O buffer self-test */
133
+struct self_test iobuf_test __self_test = {
134
+	.name = "iobuf",
135
+	.exec = iobuf_test_exec,
136
+};

+ 1
- 0
src/tests/tests.c View File

@@ -67,3 +67,4 @@ REQUIRE_OBJECT ( profile_test );
67 67
 REQUIRE_OBJECT ( setjmp_test );
68 68
 REQUIRE_OBJECT ( pccrc_test );
69 69
 REQUIRE_OBJECT ( linebuf_test );
70
+REQUIRE_OBJECT ( iobuf_test );

Loading…
Cancel
Save