Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <CA+T2pCHgA0Vy5vb+r_SNof62qzptfddW6R-CHUgnuOrzfHSRZA@mail.gmail.com>
Date: Fri, 17 Nov 2017 13:52:05 -0600
From: William Pitcock <nenolod@...eferenced.org>
To: musl@...ts.openwall.com
Subject: Re: [PATCH v7] stdio: implement fopencookie(3)

Hi,

On Fri, Nov 17, 2017 at 11:35 AM, Rich Felker <dalias@...c.org> wrote:
> On Thu, Nov 16, 2017 at 06:00:55AM +0000, William Pitcock wrote:
>> The fopencookie(3) function allows the programmer to create a custom
>> stdio implementation, using four hook functions which operate on a
>> "cookie" data type.
>>
>> [...]
>> +
>> +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;
>> +}
>
> At least the following bugs are still present:
>
> 1. If len==1, the read func is called twice, and might block the
>    second time even though the requested read of 1 byte succeeded.
>
> 2. The return value is wrong. It must be between 0 and len and
>    represent the amount of data made available to the caller. Buffer
>    filling is not supposed to be included in that since it's an
>    internal matter, not something the caller sees.
>
> 3. A return value <=0 from buffer filling step is not cause to set the
>    EOF or error indicators, since the part the caller requested
>    succeeded. But see below...
>
> Regarding issues 1 and 3, I think the right fix is to make the first
> call conditional on len>1, and always use len-1. This makes it so the
> second (possibly only) read call is never redundant/always part of the
> caller-requested read, so that you can honor its EOF or error result.
> Otherwise errors could be lost.
>
> Note that you also need to be able to handle the case where there is
> no buffer, in case setvbuf was called to disable buffering for the
> stream. In that case only the first read should happen and it should
> have full length len, not len-1.
>
> Really all of this logic should be identical to what's in
> __stdio_read.c, but with calls to the read backend rather than readv.

I believe I have reworked the read logic to match __stdio_read.c more closely.

I will be sending v8 shortly, which I hope to be the final version.

>
>> +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;
>> +}
>
> I don't see any bugs here.
>
>> +static off_t cookieseek(FILE *f, off_t off, int whence)
>> +{
>> +     struct fcookie *fc = f->cookie;
>> +     int res;
>> +     if (whence > 2) {
>
> Needs to be 2U. Otherwise you don't catch negative.

Done.

>> +             errno = EINVAL;
>> +             return -1;
>> +     }
>> +     if (!fc->iofuncs.seek) {
>> +             errno = ENOTSUP;
>> +             return -1;
>> +     }
>
> Is there a reason to prefer ENOTSUP here? Generally ESPIPE is the
> error for non-seekable FILE types, but maybe there's a reason
> something else is preferred here.

FreeBSD's implementation[1] used ENOTSUP for this case.

[1]: https://github.com/freebsd/freebsd/blob/master/lib/libc/stdio/fopencookie.c#L140-L143

glibc does not seem to set any errno at all for this case.

I think it is good to follow established convention though, since the
FreeBSD implementation does set errno, by matching theirs.

>
>> +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, 0, sizeof(FILE));
>
> Largely style, but I would prefer memset(&f->f, 0, sizeof f->f) (i.e.
> not encoding a hidden assumption that f->f is first member and always
> using sizeof the object pointed to by first arg rather than a type to
> ensure consistency);

Done.

>
>> +
>> +     /* 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;
>
> Must be f->buf+UNGET. Otherwise ungetc will underflow.

Done.

>
>> +     f->f.buf_size = BUFSIZ;
>> +     f->f.lbf = EOF;
>> +
>> +     /* enable opportunistic stdio locking */
>> +     f->f.lock = 0;
>
> This comment seems wrong. I think the line is unnecessary/redundant
> since you memset, probably leftover from when you had it as -1.

I removed it from v8.

William

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.