@@ -85,7 +85,6 @@ static const zfs_ioc_t ioc_skip[] = {
8585 ZFS_IOC_DSOBJ_TO_DSNAME ,
8686 ZFS_IOC_OBJ_TO_PATH ,
8787 ZFS_IOC_POOL_SET_PROPS ,
88- ZFS_IOC_POOL_GET_PROPS ,
8988 ZFS_IOC_SET_FSACL ,
9089 ZFS_IOC_GET_FSACL ,
9190 ZFS_IOC_SHARE ,
@@ -125,11 +124,136 @@ static const zfs_ioc_t ioc_skip[] = {
125124 lzc_ioctl_test(ioc, name, req, opt, err, wild); \
126125 } while (0)
127126
127+ #define IOC_INPUT_TEST_INJECT (ioc , name , innvl ) \
128+ do { \
129+ active_test = __func__ + 5; \
130+ lzc_ioctl_run_impl(ioc, name, innvl, 0, B_TRUE); \
131+ } while (0)
132+
133+ /*
134+ * Given a zfs_cmd_t containing an already packed nvlist in zc->zc_nvlist_src,
135+ * and its original innvl, look in innvl for the last string nvpair, or last
136+ * string array nvpair, and remove the string terminator. The idea is to
137+ * corrupt the nvlist string value so that anyone doing a strlen() on it will
138+ * read past the end of the packed nvlist buffer and trigger a crash.
139+ */
140+ static void
141+ do_bad_string (zfs_cmd_t * zc , nvlist_t * innvl )
142+ {
143+ nvpair_t * elem = NULL ;
144+ nvpair_t * lastseen = NULL ;
145+ const char * str = NULL ;
146+ const char * * arr ;
147+ uint_t n ;
148+ char * off ;
149+ char * packed ;
150+ uint64_t size , off_size ;
151+
152+ while ((elem = nvlist_next_nvpair (innvl , elem )) != NULL ) {
153+ if ((nvpair_type (elem ) == DATA_TYPE_STRING ) ||
154+ (nvpair_type (elem ) == DATA_TYPE_STRING_ARRAY ))
155+ lastseen = elem ;
156+ }
157+
158+ if (lastseen == NULL )
159+ return ; /* No strings */
160+
161+ /*
162+ * Lookup either the last string, or the last string in the last
163+ * string array in the nvlist. We will use this to corrupt from the
164+ * string to the end of the nvlist buffer. Any attempts to strlen this
165+ * string should run pass the end of the packed buffer.
166+ */
167+ if (nvpair_value_string (lastseen , & str ) != 0 ) {
168+ if (nvpair_value_string_array (lastseen , & arr , & n ) == 0 )
169+ str = arr [n - 1 ];
170+ }
171+
172+ /*
173+ * We now have the last string. Corrupt everything from the NULL
174+ * terminator byte for the last string to the end of the packed nvlist
175+ * buffer.
176+ */
177+ packed = (char * )zc -> zc_nvlist_src ;
178+ size = zc -> zc_nvlist_src_size ;
179+
180+ off = memmem (packed , size , str , strlen (str ));
181+ off_size = strlen (str );
182+
183+ memset (& off [off_size - 1 ], '!' , (packed + size ) -
184+ (& off [off_size - 1 ]));
185+
186+ }
187+
188+ /*
189+ * For each byte in the packed nvlist list in zc, corrupt a single byte, then
190+ * try doing the ioctl. This tests how well the kernel handles fuzzed nvlists.
191+ *
192+ * NOTE - make sure you are doing this with a "safe" ioctl! You don't want to
193+ * run this on an ioctl that can potentially corrupt data (like a zpool create).
194+ */
195+ static void
196+ do_fuzz (int zfs_fd , zfs_ioc_t ioc , zfs_cmd_t * zc )
197+ {
198+ uint64_t size ;
199+ uint64_t i ;
200+ unsigned char old = 0 ;
201+ unsigned char * pos ;
202+ zfs_cmd_t orig_zc = * zc ;
203+
204+ pos = (unsigned char * ) zc -> zc_nvlist_src ;
205+ size = zc -> zc_nvlist_src_size ;
206+
207+ /*
208+ * Fuzz each byte in the packed nvlist, one byte at a time, and do the
209+ * ioctl. If the kernel doesn't crash, then the test passed.
210+ */
211+ for (i = 0 ; i < size ; i ++ ) {
212+ /* Restore the previously corrupted byte */
213+ if (i > 0 )
214+ pos [i - 1 ] = old ;
215+
216+ old = pos [i ];
217+
218+ /* Corrupt the new byte */
219+ pos [i ]++ ;
220+
221+ /*
222+ * Do the ioctl and ignore the return code. We just want to
223+ * see if the kernel panics.
224+ */
225+ lzc_ioctl_fd (zfs_fd , ioc , zc );
226+
227+ /*
228+ * Restore 'zc' with original fields since the ioctl may
229+ * have modified them.
230+ */
231+ * zc = orig_zc ;
232+ }
233+ /* Restore last byte */
234+ if (i > 0 )
235+ pos [i - 1 ] = old ;
236+
237+ /*
238+ * Try fuzzing the packed nvlist size field. Test it with one byte
239+ * bigger and one byte smaller than the current value.
240+ */
241+ zc -> zc_nvlist_src_size -- ;
242+ lzc_ioctl_fd (zfs_fd , ioc , zc );
243+
244+ zc -> zc_nvlist_src_size += 2 ;
245+ lzc_ioctl_fd (zfs_fd , ioc , zc );
246+
247+ /* Restore to normal */
248+ zc -> zc_nvlist_src_size -= 1 ;
249+ }
250+
128251/*
129252 * run a zfs ioctl command, verify expected results and log failures
130253 */
131254static void
132- lzc_ioctl_run (zfs_ioc_t ioc , const char * name , nvlist_t * innvl , int expected )
255+ lzc_ioctl_run_impl (zfs_ioc_t ioc , const char * name , nvlist_t * innvl ,
256+ int expected , boolean_t do_corrupt )
133257{
134258 zfs_cmd_t zc = {"\0" };
135259 char * packed = NULL ;
@@ -160,10 +284,30 @@ lzc_ioctl_run(zfs_ioc_t ioc, const char *name, nvlist_t *innvl, int expected)
160284 zc .zc_nvlist_dst_size = MAX (size * 2 , 128 * 1024 );
161285 zc .zc_nvlist_dst = (uint64_t )(uintptr_t )malloc (zc .zc_nvlist_dst_size );
162286
287+ if (do_corrupt ) {
288+ /*
289+ * Try changing bytes in the packed nvlist to see if it will
290+ * panic the kernel when you do the ioctl.
291+ */
292+ do_fuzz (zfs_fd , ioc , & zc );
293+
294+ /*
295+ * Corrupt the last string in the packed nvlist so it has no
296+ * NULL terminator.
297+ */
298+ do_bad_string (& zc , innvl );
299+
300+ }
301+
163302 if (lzc_ioctl_fd (zfs_fd , ioc , & zc ) != 0 )
164303 error = errno ;
165304
166- if (error != expected ) {
305+ /*
306+ * If we're corrupting the nvlist we don't care about the specific
307+ * error code that gets returned, as it could be one of many. We only
308+ * care if it panics the kernel.
309+ */
310+ if (!do_corrupt && error != expected ) {
167311 unexpected_failures = B_TRUE ;
168312 (void ) fprintf (stderr , "%s: Unexpected result with %s, "
169313 "error %d (expecting %d)\n" ,
@@ -174,6 +318,12 @@ lzc_ioctl_run(zfs_ioc_t ioc, const char *name, nvlist_t *innvl, int expected)
174318 free ((void * )(uintptr_t )zc .zc_nvlist_dst );
175319}
176320
321+ static void
322+ lzc_ioctl_run (zfs_ioc_t ioc , const char * name , nvlist_t * innvl , int expected )
323+ {
324+ return (lzc_ioctl_run_impl (ioc , name , innvl , expected , B_FALSE ));
325+ }
326+
177327/*
178328 * Test each ioc for the following ioctl input errors:
179329 * ZFS_ERR_IOC_ARG_UNAVAIL an input argument is not supported by kernel
@@ -310,6 +460,7 @@ test_log_history(const char *pool)
310460 fnvlist_add_string (required , "message" , "input check" );
311461
312462 IOC_INPUT_TEST (ZFS_IOC_LOG_HISTORY , pool , required , NULL , 0 );
463+ IOC_INPUT_TEST_INJECT (ZFS_IOC_LOG_HISTORY , pool , required );
313464
314465 nvlist_free (required );
315466}
@@ -791,6 +942,20 @@ test_set_bootenv(const char *pool)
791942 nvlist_free (required );
792943}
793944
945+ static void
946+ test_zpool_get (const char * pool )
947+ {
948+ const char * strs [] = {ZPOOL_DEDUPCACHED_PROP_NAME };
949+ nvlist_t * optional = fnvlist_alloc ();
950+
951+ fnvlist_add_string_array (optional , ZPOOL_GET_PROPS_NAMES , strs , 1 );
952+
953+ IOC_INPUT_TEST (ZFS_IOC_POOL_GET_PROPS , pool , NULL , optional , 0 );
954+ IOC_INPUT_TEST_INJECT (ZFS_IOC_POOL_GET_PROPS , pool , optional );
955+
956+ nvlist_free (optional );
957+ }
958+
794959static void
795960zfs_ioc_input_tests (const char * pool )
796961{
@@ -885,6 +1050,7 @@ zfs_ioc_input_tests(const char *pool)
8851050
8861051 test_scrub (pool );
8871052
1053+ test_zpool_get (pool );
8881054 /*
8891055 * cleanup
8901056 */
0 commit comments