From mboxrd@z Thu Jan 1 00:00:00 1970 X-Msuck: nntp://news.gmane.org/gmane.linux.lib.musl.general/12109 Path: news.gmane.org!.POSTED!not-for-mail From: William Pitcock Newsgroups: gmane.linux.lib.musl.general Subject: [PATCH v8] stdio: implement fopencookie(3) Date: Fri, 17 Nov 2017 20:46:36 +0000 Message-ID: <20171117204636.27538-1-nenolod@dereferenced.org> Reply-To: musl@lists.openwall.com NNTP-Posting-Host: blaine.gmane.org X-Trace: blaine.gmane.org 1510951619 25694 195.159.176.226 (17 Nov 2017 20:46:59 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Fri, 17 Nov 2017 20:46:59 +0000 (UTC) Cc: William Pitcock To: musl@lists.openwall.com Original-X-From: musl-return-12125-gllmg-musl=m.gmane.org@lists.openwall.com Fri Nov 17 21:46:55 2017 Return-path: Envelope-to: gllmg-musl@m.gmane.org Original-Received: from mother.openwall.net ([195.42.179.200]) by blaine.gmane.org with smtp (Exim 4.84_2) (envelope-from ) id 1eFnXI-0006Kw-KB for gllmg-musl@m.gmane.org; Fri, 17 Nov 2017 21:46:52 +0100 Original-Received: (qmail 22189 invoked by uid 550); 17 Nov 2017 20:46:57 -0000 Mailing-List: contact musl-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Original-Received: (qmail 22144 invoked from network); 17 Nov 2017 20:46:57 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dereferenced-org.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id; bh=yMJrJ4q2fuDyQP2d61a0KsVjtwJdbXiX8f9S48HY2eA=; b=ehXPqmxczfdQYOW0JGEMa4ec46EZ0oVEi+ggBGq+bwmaL+COZewgEfwjOvYn1c5bYo OA6o4mdL+s7QQ3KWueawfkLfNDEXFwHI5Tffpiir18Ny7JkxsgEvGSvzKrcbdw3tz8O9 2kh7kIXOC/D0R1dhJZD7zuUdYDrP90ddZ3EmcGv4Y63An0bnQv9S7RdHL3iqlux7jp2x a36F8NnlD7Y0sO7HoU2lPneZ7McxrFDmKvIx88Gq18O+NwjxmoVX/V+E/o8JlmTbi3k5 X3S3J7/1MoAIGlakjLRQUKLAj6gtcoGJsmJNxAb/6JFyEaPRJOEMaYeY3b1jVWJjMvNr AcWw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=yMJrJ4q2fuDyQP2d61a0KsVjtwJdbXiX8f9S48HY2eA=; b=L9uBYvuF/dTddOzZPEA6E0yvUAuhVvJYgbAnCy1Xa0U74XIYPhaY96KT5wnAdJ+OqJ k6U3DHs55vVP3u+DY0Xs3mdWoU73+qRGlOfONvNx9x8FneFs7QQ/SpxiGjipCMRdkxdf 4U0njXCorwi3JyPHS7/afOSlPowhY2c5d5/xnsRHE6Em018FxGganmgQRBmRF2FuCRUR AFl1lsNb5m6Vv15gw7+G8K7t5OvciaOHPWguxBLpCyJm0qr91+PULoD567fEx9aACi/k aosfo/bQeQe36zcjNX1wp2k2QVeWyg3PkgSqCmadFnQ9tTVZu5JjGyRsGC2S5hObsj2B nPNQ== X-Gm-Message-State: AJaThX4Y9HP+dUZzV2EyLVdxt1rkP5gRfFflUpWd8yO2b1yAVbZt3bkH ydnRifcGliKfAayWpYHPjpP/kdO+ X-Google-Smtp-Source: AGs4zMYSabLd1CQsD9IVKCgYdyEM0b468gn8ypBvl/IwT7WJ3cNHSanucYaZ8x7DrrnUJwtt0nXf3w== X-Received: by 10.157.13.71 with SMTP id 65mr2394682oti.373.1510951604873; Fri, 17 Nov 2017 12:46:44 -0800 (PST) X-Mailer: git-send-email 2.15.0 Xref: news.gmane.org gmane.linux.lib.musl.general:12109 Archived-At: The fopencookie(3) function allows the programmer to create a custom stdio implementation, using four hook functions which operate on a "cookie" data type. Changelog: v8: - fix possible ungetc() underflow - style cleanups v7: - include GNU typedefs for cookie i/o functions v6: - remove pointer arithmetic instead using a structure to contain the parent FILE object - set F_ERR flag where appropriate - style fixes - fix stdio readahead to handle single-byte read case (as pointed out by dalias, tested by custom fuzzer) - handle seek return values correctly (found by fuzzing) v5: - implement stdio readahead buffering support v4: - remove parameter names from header function declarations v3: - remove spurious `struct winsize` - make f->lock unconditionally 0 v2: - properly implement stdio buffering v1: - initial proof of concept --- include/stdio.h | 14 +++++ src/stdio/fopencookie.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 src/stdio/fopencookie.c diff --git a/include/stdio.h b/include/stdio.h index 884d2e6a..2932c76f 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -182,6 +182,20 @@ int vasprintf(char **, const char *, __isoc_va_list); #ifdef _GNU_SOURCE char *fgets_unlocked(char *, int, FILE *); int fputs_unlocked(const char *, FILE *); + +typedef ssize_t (cookie_read_function_t)(void *, char *, size_t); +typedef ssize_t (cookie_write_function_t)(void *, const char *, size_t); +typedef int (cookie_seek_function_t)(void *, off_t *, int); +typedef int (cookie_close_function_t)(void *); + +typedef struct { + cookie_read_function_t *read; + cookie_write_function_t *write; + cookie_seek_function_t *seek; + cookie_close_function_t *close; +} cookie_io_functions_t; + +FILE *fopencookie(void *, const char *, cookie_io_functions_t); #endif #if defined(_LARGEFILE64_SOURCE) || defined(_GNU_SOURCE) diff --git a/src/stdio/fopencookie.c b/src/stdio/fopencookie.c new file mode 100644 index 00000000..b240a887 --- /dev/null +++ b/src/stdio/fopencookie.c @@ -0,0 +1,138 @@ +#define _GNU_SOURCE +#include "stdio_impl.h" +#include +#include +#include +#include +#include + +struct fcookie { + void *cookie; + cookie_io_functions_t iofuncs; +}; + +struct cookie_FILE { + FILE f; + struct fcookie fc; + unsigned char buf[UNGET+BUFSIZ]; +}; + +static size_t cookieread(FILE *f, unsigned char *buf, size_t len) +{ + struct fcookie *fc = f->cookie; + ssize_t ret = -1; + size_t remain = len, readlen = 0; + + if (!fc->iofuncs.read) goto bail; + + ret = fc->iofuncs.read(fc->cookie, (char *) buf, len > 1 ? (len - 1) : 1); + if (ret <= 0) goto bail; + + readlen += ret; + remain -= ret; + + f->rpos = f->buf; + ret = fc->iofuncs.read(fc->cookie, (char *) f->rpos, f->buf_size); + if (ret <= 0) goto bail; + f->rend = f->rpos + ret; + + if (remain > 0) { + if (remain > f->buf_size) remain = f->buf_size; + memcpy(buf + readlen, f->rpos, remain); + readlen += remain; + f->rpos += remain; + } + + return readlen; + +bail: + f->flags |= F_EOF ^ ((F_ERR^F_EOF) & ret); + f->rpos = f->rend = f->buf; + return readlen; +} + +static size_t cookiewrite(FILE *f, const unsigned char *buf, size_t len) +{ + struct fcookie *fc = f->cookie; + ssize_t ret; + size_t len2 = f->wpos - f->wbase; + if (!fc->iofuncs.write) return len; + if (len2) { + f->wpos = f->wbase; + if (cookiewrite(f, f->wpos, len2) < len2) return 0; + } + ret = fc->iofuncs.write(fc->cookie, (const char *) buf, len); + if (ret < 0) { + f->wpos = f->wbase = f->wend = 0; + f->flags |= F_ERR; + return 0; + } + return ret; +} + +static off_t cookieseek(FILE *f, off_t off, int whence) +{ + struct fcookie *fc = f->cookie; + int res; + if (whence > 2U) { + errno = EINVAL; + return -1; + } + if (!fc->iofuncs.seek) { + errno = ENOTSUP; + return -1; + } + res = fc->iofuncs.seek(fc->cookie, &off, whence); + if (res < 0) + return res; + return off; +} + +static int cookieclose(FILE *f) +{ + struct fcookie *fc = f->cookie; + if (fc->iofuncs.close) return fc->iofuncs.close(fc->cookie); + return 0; +} + +FILE *fopencookie(void *cookie, const char *mode, cookie_io_functions_t iofuncs) +{ + struct cookie_FILE *f; + + /* Check for valid initial mode character */ + if (!strchr("rwa", *mode)) { + errno = EINVAL; + return 0; + } + + /* Allocate FILE+fcookie+buffer or fail */ + if (!(f=malloc(sizeof *f))) return 0; + + /* Zero-fill only the struct, not the buffer */ + memset(&f->f, 0, sizeof f->f); + + /* Impose mode restrictions */ + if (!strchr(mode, '+')) f->f.flags = (*mode == 'r') ? F_NOWR : F_NORD; + + /* Set up our fcookie */ + f->fc.cookie = cookie; + f->fc.iofuncs.read = iofuncs.read; + f->fc.iofuncs.write = iofuncs.write; + f->fc.iofuncs.seek = iofuncs.seek; + f->fc.iofuncs.close = iofuncs.close; + + f->f.fd = -1; + f->f.cookie = &f->fc; + f->f.buf = f->buf + UNGET; + f->f.buf_size = BUFSIZ; + f->f.lbf = EOF; + + /* Initialize op ptrs. No problem if some are unneeded. */ + f->f.read = cookieread; + f->f.write = cookiewrite; + f->f.seek = cookieseek; + f->f.close = cookieclose; + + /* Add new FILE to open file list */ + return __ofl_add(&f->f); +} -- 2.15.0