Changeset View
Standalone View
sys/kern/kern_sendfile.c
Show First 20 Lines • Show All 1,001 Lines • ▼ Show 20 Lines | #endif | ||||
*/ | */ | ||||
if ((error = fget_read(td, uap->fd, &cap_pread_rights, &fp)) != 0) | if ((error = fget_read(td, uap->fd, &cap_pread_rights, &fp)) != 0) | ||||
goto out; | goto out; | ||||
error = fo_sendfile(fp, uap->s, hdr_uio, trl_uio, uap->offset, | error = fo_sendfile(fp, uap->s, hdr_uio, trl_uio, uap->offset, | ||||
uap->nbytes, &sbytes, uap->flags, td); | uap->nbytes, &sbytes, uap->flags, td); | ||||
fdrop(fp, td); | fdrop(fp, td); | ||||
if (uap->sbytes != NULL) | /* | ||||
copyout(&sbytes, uap->sbytes, sizeof(off_t)); | * Only copy out the number of bytes if fo_sendfile did not fail, or | ||||
markj: This should be declared at the beginning of the function per style(9). | |||||
* if it failed with EAGAIN, EBUSY, or EINTR. | |||||
Done Inline ActionsWhy are EAGAIN and/or EINTR special? imp: Why are EAGAIN and/or EINTR special?
| |||||
Done Inline ActionsApologies for not making this clearer in the review description. I want to ensure that if sendfile fails with a non-fatal error (EAGAIN/EINTR), the error isn't squashed by the copyout operation failing. This was a conclusion that I made based on our discussion on the GitHub PR I submitted a few months ago:
Sidenote: reading through the requirements again, it seems that EBUSY would need this treatment as well as it says "Partial data may have been sent": [EBUSY] A busy page was encountered and SF_NODISKIO had been specified. Partial data may have been sent. ngie: Apologies for not making this clearer in the review description.
I want to ensure that if… | |||||
Done Inline ActionsI typoed:
... fatal error (not EAGAIN/EINTR) ... ngie: I typoed:
> I want to ensure ... non-fatal error (EAGAIN/EINTR) ...
... fatal error (not… | |||||
*/ | |||||
if (uap->sbytes != NULL && | |||||
(error == 0 || error == EAGAIN || error == EBUSY || | |||||
error == EINTR)) { | |||||
int copyout_error; | |||||
Done Inline ActionsThe comment is formatted oddly, and doesn't seem very useful to me. It's pretty clear what the code is doing, and whether or not copyout() fails depends entirely on whether the caller provided a valid address. markj: The comment is formatted oddly, and doesn't seem very useful to me. It's pretty clear what the… | |||||
Done Inline ActionsYeah... I shouldn't have been split across 3 lines. Fixed to be just 2 lines. As to the comment itself, I'll remove it (you're right -- it's overkill). ngie: Yeah... I shouldn't have been split across 3 lines. Fixed to be just 2 lines.
As to the… | |||||
copyout_error = copyout(&sbytes, uap->sbytes, sizeof(off_t)); | |||||
/* | |||||
Done Inline ActionsIs there some justification for overwriting a non-zero error with copyout()'s error? markj: Is there some justification for overwriting a non-zero `error` with copyout()'s error? | |||||
Done Inline ActionsSome of the APIs state that they will return the number of bytes sent in sbytes, if interrupted, etc. What if these errors are triggered, but an EFAULT issue occurs because a bad address is passed in? It would make the value returned via sbytes invalid/misleading (hence, the need for the revision). Per copyout(9), EFAULT is the only error that should be returned on failure: RETURN VALUES The copy functions return 0 on success. All but copystr() return EFAULT if a bad address is encountered. The copyin_nofault() and copyout_nofault() functions return EFAULT if a page fault occurs. The copystr() and copyinstr() functions return ENAMETOOLONG if the string is longer than len bytes. ngie: Some of the APIs state that they will return the number of bytes sent in `sbytes`, if… | |||||
Not Done Inline ActionsBut if a copyout() to sbytes returns EFAULT, then by definition the userspace program cannot access the memory at sbytes. markj: But if a copyout() to sbytes returns EFAULT, then by definition the userspace program cannot… | |||||
Done Inline ActionsYes. That's the important point; that's why I chose to have the value from copyout overrides the value from the failed sendfile call. Right now, if data is sent and sendfile(2) fails with EAGAIN, but the user provided a bogus pointer, it could result in programs calling sendfile(2) making decisions about sbytes being valid, whereas the data in sbytes is totally bogus. ngie: Yes. That's the important point; that's why I chose to have the value from copyout overrides… | |||||
Not Done Inline ActionsIf copyout() fails, an attempt by the program to access the data in sbytes will cause a segfault. Do you have a real-world program to demonstrate your concern? markj: If copyout() fails, an attempt by the program to access the data in `sbytes` will cause a… | |||||
Done Inline ActionsThe "real-world" program I have (so far) is the testcase that was just committed in rS343362. ngie: The "real-world" program I have (so far) is the testcase that was just committed in rS343362. | |||||
Not Done Inline ActionsIMHO EFAULT on sbytes should not overwrite any other error. Anything that errored before this line is likely to be more significant in terms of why it failed. Trying to identify a blacklist of errors to not overwrite is risk prone; overwriting EAGAIN would be really bad. Less complexity is better so avoiding any kind of special list is good. Consider the bigger picture here though. bdrewery: IMHO `EFAULT` on `sbytes` should not overwrite any other error. Anything that errored before… | |||||
Done Inline Actions
This is a good assumption to operate by, in most cases, but it gets marginally harder when dealing with programs that might concurrently modify memory in an malloc'ed arena, poorly (bad locking or pointer errors are what come to mind). I'm mentioning this because I saw similar issues at my most recent employer, just on Linux (and in this case, with libaio, et al). It was even more difficult because the top-layer implementation used C++, but the underlying code was written in C, and the developers/PEs weren't expected to understand or write C code :/... I'm advocating/optimizing for better developer experience with sendfile(2) and to ensure that the sendfile(2) API contract doesn't become overly conditional, e.g., will return -1/EFAULT, unless you provided me with bogus storage for sbytes, then it may either return -1/EFAULT if there wasn't an error, or crash when you access the memory pointed to by sbytes :(. ngie: > @bdrewery: Bad code that doesn't check the return value of `sendfile` will just crash if… | |||||
* Ensure that copyout's error doesn't mask an existing error | |||||
* (EAGAIN, etc), if the operation was successful. | |||||
*/ | |||||
error = copyout_error != 0 ? copyout_error : error; | |||||
} | |||||
out: | out: | ||||
free(hdr_uio, M_IOV); | free(hdr_uio, M_IOV); | ||||
free(trl_uio, M_IOV); | free(trl_uio, M_IOV); | ||||
return (error); | return (error); | ||||
} | } | ||||
/* | /* | ||||
Show All 34 Lines |
This should be declared at the beginning of the function per style(9).