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