1 /* Test for libio vtables and their validation. Common code.
2 Copyright (C) 2018-2022 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19 /* This test provides some coverage for how various stdio functions
20 use the vtables in FILE * objects. The focus is mostly on which
21 functions call which methods, not so much on validating data
22 processing. An initial series of tests check that custom vtables
23 do not work without activation through _IO_init.
24
25 Note: libio vtables are deprecated feature. Do not use this test
26 as a documentation source for writing custom vtables. See
27 fopencookie for a different way of creating custom stdio
28 streams. */
29
30 #include <stdbool.h>
31 #include <string.h>
32 #include <support/capture_subprocess.h>
33 #include <support/check.h>
34 #include <support/namespace.h>
35 #include <support/support.h>
36 #include <support/test-driver.h>
37 #include <support/xunistd.h>
38
39 #include "libioP.h"
40
41 /* Data shared between the test subprocess and the test driver in the
42 parent. Note that *shared is reset at the start of the check_call
43 function. */
44 struct shared
45 {
46 /* Expected file pointer for method calls. */
47 FILE *fp;
48
49 /* If true, assume that a call to _IO_init is needed to enable
50 custom vtables. */
51 bool initially_disabled;
52
53 /* Requested return value for the methods which have one. */
54 int return_value;
55
56 /* A value (usually a character) recorded by some of the methods
57 below. */
58 int value;
59
60 /* Likewise, for some data. */
61 char buffer[16];
62 size_t buffer_length;
63
64 /* Total number of method calls. */
65 unsigned int calls;
66
67 /* Individual method call counts. */
68 unsigned int calls_finish;
69 unsigned int calls_overflow;
70 unsigned int calls_underflow;
71 unsigned int calls_uflow;
72 unsigned int calls_pbackfail;
73 unsigned int calls_xsputn;
74 unsigned int calls_xsgetn;
75 unsigned int calls_seekoff;
76 unsigned int calls_seekpos;
77 unsigned int calls_setbuf;
78 unsigned int calls_sync;
79 unsigned int calls_doallocate;
80 unsigned int calls_read;
81 unsigned int calls_write;
82 unsigned int calls_seek;
83 unsigned int calls_close;
84 unsigned int calls_stat;
85 unsigned int calls_showmanyc;
86 unsigned int calls_imbue;
87 } *shared;
88
89 /* Method implementations which increment the counters in *shared. */
90
91 static void
log_method(FILE * fp,const char * name)92 log_method (FILE *fp, const char *name)
93 {
94 if (test_verbose > 0)
95 printf ("info: %s (%p) called\n", name, fp);
96 }
97
98 static void
method_finish(FILE * fp,int dummy)99 method_finish (FILE *fp, int dummy)
100 {
101 log_method (fp, __func__);
102 TEST_VERIFY (fp == shared->fp);
103 ++shared->calls;
104 ++shared->calls_finish;
105 }
106
107 static int
method_overflow(FILE * fp,int ch)108 method_overflow (FILE *fp, int ch)
109 {
110 log_method (fp, __func__);
111 TEST_VERIFY (fp == shared->fp);
112 ++shared->calls;
113 ++shared->calls_overflow;
114 shared->value = ch;
115 return shared->return_value;
116 }
117
118 static int
method_underflow(FILE * fp)119 method_underflow (FILE *fp)
120 {
121 log_method (fp, __func__);
122 TEST_VERIFY (fp == shared->fp);
123 ++shared->calls;
124 ++shared->calls_underflow;
125 return shared->return_value;
126 }
127
128 static int
method_uflow(FILE * fp)129 method_uflow (FILE *fp)
130 {
131 log_method (fp, __func__);
132 TEST_VERIFY (fp == shared->fp);
133 ++shared->calls;
134 ++shared->calls_uflow;
135 return shared->return_value;
136 }
137
138 static int
method_pbackfail(FILE * fp,int ch)139 method_pbackfail (FILE *fp, int ch)
140 {
141 log_method (fp, __func__);
142 TEST_VERIFY (fp == shared->fp);
143 ++shared->calls;
144 ++shared->calls_pbackfail;
145 shared->value = ch;
146 return shared->return_value;
147 }
148
149 static size_t
method_xsputn(FILE * fp,const void * data,size_t n)150 method_xsputn (FILE *fp, const void *data, size_t n)
151 {
152 log_method (fp, __func__);
153 TEST_VERIFY (fp == shared->fp);
154 ++shared->calls;
155 ++shared->calls_xsputn;
156
157 size_t to_copy = n;
158 if (n > sizeof (shared->buffer))
159 to_copy = sizeof (shared->buffer);
160 memcpy (shared->buffer, data, to_copy);
161 shared->buffer_length = to_copy;
162 return to_copy;
163 }
164
165 static size_t
method_xsgetn(FILE * fp,void * data,size_t n)166 method_xsgetn (FILE *fp, void *data, size_t n)
167 {
168 log_method (fp, __func__);
169 TEST_VERIFY (fp == shared->fp);
170 ++shared->calls;
171 ++shared->calls_xsgetn;
172 return 0;
173 }
174
175 static off64_t
method_seekoff(FILE * fp,off64_t offset,int dir,int mode)176 method_seekoff (FILE *fp, off64_t offset, int dir, int mode)
177 {
178 log_method (fp, __func__);
179 TEST_VERIFY (fp == shared->fp);
180 ++shared->calls;
181 ++shared->calls_seekoff;
182 return shared->return_value;
183 }
184
185 static off64_t
method_seekpos(FILE * fp,off64_t offset,int mode)186 method_seekpos (FILE *fp, off64_t offset, int mode)
187 {
188 log_method (fp, __func__);
189 TEST_VERIFY (fp == shared->fp);
190 ++shared->calls;
191 ++shared->calls_seekpos;
192 return shared->return_value;
193 }
194
195 static FILE *
method_setbuf(FILE * fp,char * buffer,ssize_t length)196 method_setbuf (FILE *fp, char *buffer, ssize_t length)
197 {
198 log_method (fp, __func__);
199 TEST_VERIFY (fp == shared->fp);
200 ++shared->calls;
201 ++shared->calls_setbuf;
202 return fp;
203 }
204
205 static int
method_sync(FILE * fp)206 method_sync (FILE *fp)
207 {
208 log_method (fp, __func__);
209 TEST_VERIFY (fp == shared->fp);
210 ++shared->calls;
211 ++shared->calls_sync;
212 return shared->return_value;
213 }
214
215 static int
method_doallocate(FILE * fp)216 method_doallocate (FILE *fp)
217 {
218 log_method (fp, __func__);
219 TEST_VERIFY (fp == shared->fp);
220 ++shared->calls;
221 ++shared->calls_doallocate;
222 return shared->return_value;
223 }
224
225 static ssize_t
method_read(FILE * fp,void * data,ssize_t length)226 method_read (FILE *fp, void *data, ssize_t length)
227 {
228 log_method (fp, __func__);
229 TEST_VERIFY (fp == shared->fp);
230 ++shared->calls;
231 ++shared->calls_read;
232 return shared->return_value;
233 }
234
235 static ssize_t
method_write(FILE * fp,const void * data,ssize_t length)236 method_write (FILE *fp, const void *data, ssize_t length)
237 {
238 log_method (fp, __func__);
239 TEST_VERIFY (fp == shared->fp);
240 ++shared->calls;
241 ++shared->calls_write;
242 return shared->return_value;
243 }
244
245 static off64_t
method_seek(FILE * fp,off64_t offset,int mode)246 method_seek (FILE *fp, off64_t offset, int mode)
247 {
248 log_method (fp, __func__);
249 TEST_VERIFY (fp == shared->fp);
250 ++shared->calls;
251 ++shared->calls_seek;
252 return shared->return_value;
253 }
254
255 static int
method_close(FILE * fp)256 method_close (FILE *fp)
257 {
258 log_method (fp, __func__);
259 TEST_VERIFY (fp == shared->fp);
260 ++shared->calls;
261 ++shared->calls_close;
262 return shared->return_value;
263 }
264
265 static int
method_stat(FILE * fp,void * buffer)266 method_stat (FILE *fp, void *buffer)
267 {
268 log_method (fp, __func__);
269 TEST_VERIFY (fp == shared->fp);
270 ++shared->calls;
271 ++shared->calls_stat;
272 return shared->return_value;
273 }
274
275 static int
method_showmanyc(FILE * fp)276 method_showmanyc (FILE *fp)
277 {
278 log_method (fp, __func__);
279 TEST_VERIFY (fp == shared->fp);
280 ++shared->calls;
281 ++shared->calls_showmanyc;
282 return shared->return_value;
283 }
284
285 static void
method_imbue(FILE * fp,void * locale)286 method_imbue (FILE *fp, void *locale)
287 {
288 log_method (fp, __func__);
289 TEST_VERIFY (fp == shared->fp);
290 ++shared->calls;
291 ++shared->calls_imbue;
292 }
293
294 /* Our custom vtable. */
295
296 static const struct _IO_jump_t jumps =
297 {
298 JUMP_INIT_DUMMY,
299 JUMP_INIT (finish, method_finish),
300 JUMP_INIT (overflow, method_overflow),
301 JUMP_INIT (underflow, method_underflow),
302 JUMP_INIT (uflow, method_uflow),
303 JUMP_INIT (pbackfail, method_pbackfail),
304 JUMP_INIT (xsputn, method_xsputn),
305 JUMP_INIT (xsgetn, method_xsgetn),
306 JUMP_INIT (seekoff, method_seekoff),
307 JUMP_INIT (seekpos, method_seekpos),
308 JUMP_INIT (setbuf, method_setbuf),
309 JUMP_INIT (sync, method_sync),
310 JUMP_INIT (doallocate, method_doallocate),
311 JUMP_INIT (read, method_read),
312 JUMP_INIT (write, method_write),
313 JUMP_INIT (seek, method_seek),
314 JUMP_INIT (close, method_close),
315 JUMP_INIT (stat, method_stat),
316 JUMP_INIT (showmanyc, method_showmanyc),
317 JUMP_INIT (imbue, method_imbue)
318 };
319
320 /* Our file implementation. */
321
322 struct my_file
323 {
324 FILE f;
325 const struct _IO_jump_t *vtable;
326 };
327
328 struct my_file
my_file_create(void)329 my_file_create (void)
330 {
331 return (struct my_file)
332 {
333 /* Disable locking, so that we do not have to set up a lock
334 pointer. */
335 .f._flags = _IO_USER_LOCK,
336
337 /* Copy the offset from the an initialized handle, instead of
338 figuring it out from scratch. */
339 .f._vtable_offset = stdin->_vtable_offset,
340
341 .vtable = &jumps,
342 };
343 }
344
345 /* Initial tests which do not enable vtable compatibility. */
346
347 /* Inhibit GCC optimization of fprintf. */
348 typedef int (*fprintf_type) (FILE *, const char *, ...);
349 static const volatile fprintf_type fprintf_ptr = &fprintf;
350
351 static void
without_compatibility_fprintf(void * closure)352 without_compatibility_fprintf (void *closure)
353 {
354 /* This call should abort. */
355 fprintf_ptr (shared->fp, " ");
356 _exit (1);
357 }
358
359 static void
without_compatibility_fputc(void * closure)360 without_compatibility_fputc (void *closure)
361 {
362 /* This call should abort. */
363 fputc (' ', shared->fp);
364 _exit (1);
365 }
366
367 static void
without_compatibility_fgetc(void * closure)368 without_compatibility_fgetc (void *closure)
369 {
370 /* This call should abort. */
371 fgetc (shared->fp);
372 _exit (1);
373 }
374
375 static void
without_compatibility_fflush(void * closure)376 without_compatibility_fflush (void *closure)
377 {
378 /* This call should abort. */
379 fflush (shared->fp);
380 _exit (1);
381 }
382
383 static void
check_for_termination(const char * name,void (* callback)(void *))384 check_for_termination (const char *name, void (*callback) (void *))
385 {
386 struct my_file file = my_file_create ();
387 shared->fp = &file.f;
388 shared->return_value = -1;
389 shared->calls = 0;
390 struct support_capture_subprocess proc
391 = support_capture_subprocess (callback, NULL);
392 support_capture_subprocess_check (&proc, name, -SIGABRT,
393 sc_allow_stderr);
394 const char *message
395 = "Fatal error: glibc detected an invalid stdio handle\n";
396 TEST_COMPARE_BLOB (proc.err.buffer, proc.err.length,
397 message, strlen (message));
398 TEST_COMPARE (shared->calls, 0);
399 support_capture_subprocess_free (&proc);
400 }
401
402 /* The test with vtable validation disabled. */
403
404 /* This function does not have a prototype in libioP.h to prevent
405 accidental use from within the library (which would disable vtable
406 verification). */
407 void _IO_init (FILE *fp, int flags);
408
409 static void
with_compatibility_fprintf(void * closure)410 with_compatibility_fprintf (void *closure)
411 {
412 TEST_COMPARE (fprintf_ptr (shared->fp, "A%sCD", "B"), 4);
413 TEST_COMPARE (shared->calls, 3);
414 TEST_COMPARE (shared->calls_xsputn, 3);
415 TEST_COMPARE_BLOB (shared->buffer, shared->buffer_length,
416 "CD", 2);
417 }
418
419 static void
with_compatibility_fputc(void * closure)420 with_compatibility_fputc (void *closure)
421 {
422 shared->return_value = '@';
423 TEST_COMPARE (fputc ('@', shared->fp), '@');
424 TEST_COMPARE (shared->calls, 1);
425 TEST_COMPARE (shared->calls_overflow, 1);
426 TEST_COMPARE (shared->value, '@');
427 }
428
429 static void
with_compatibility_fgetc(void * closure)430 with_compatibility_fgetc (void *closure)
431 {
432 shared->return_value = 'X';
433 TEST_COMPARE (fgetc (shared->fp), 'X');
434 TEST_COMPARE (shared->calls, 1);
435 TEST_COMPARE (shared->calls_uflow, 1);
436 }
437
438 static void
with_compatibility_fflush(void * closure)439 with_compatibility_fflush (void *closure)
440 {
441 TEST_COMPARE (fflush (shared->fp), 0);
442 TEST_COMPARE (shared->calls, 1);
443 TEST_COMPARE (shared->calls_sync, 1);
444 }
445
446 /* Call CALLBACK in a subprocess, after setting up a custom file
447 object and updating shared->fp. */
448 static void
check_call(const char * name,void (* callback)(void *),bool initially_disabled)449 check_call (const char *name, void (*callback) (void *),
450 bool initially_disabled)
451 {
452 *shared = (struct shared)
453 {
454 .initially_disabled = initially_disabled,
455 };
456
457 /* Set up a custom file object. */
458 struct my_file file = my_file_create ();
459 shared->fp = &file.f;
460 if (shared->initially_disabled)
461 _IO_init (shared->fp, file.f._flags);
462
463 if (test_verbose > 0)
464 printf ("info: calling test %s\n", name);
465 support_isolate_in_subprocess (callback, NULL);
466 }
467
468 /* Run the tests. INITIALLY_DISABLED indicates whether custom vtables
469 are disabled when the test starts. */
470 static int
run_tests(bool initially_disabled)471 run_tests (bool initially_disabled)
472 {
473 /* The test relies on fatal error messages being printed to standard
474 error. */
475 setenv ("LIBC_FATAL_STDERR_", "1", 1);
476
477 shared = support_shared_allocate (sizeof (*shared));
478 shared->initially_disabled = initially_disabled;
479
480 if (initially_disabled)
481 {
482 check_for_termination ("fprintf", without_compatibility_fprintf);
483 check_for_termination ("fputc", without_compatibility_fputc);
484 check_for_termination ("fgetc", without_compatibility_fgetc);
485 check_for_termination ("fflush", without_compatibility_fflush);
486 }
487
488 check_call ("fprintf", with_compatibility_fprintf, initially_disabled);
489 check_call ("fputc", with_compatibility_fputc, initially_disabled);
490 check_call ("fgetc", with_compatibility_fgetc, initially_disabled);
491 check_call ("fflush", with_compatibility_fflush, initially_disabled);
492
493 support_shared_free (shared);
494 shared = NULL;
495
496 return 0;
497 }
498