new year new rules new lines
We have a new edition of POSIX, which means new rules, new regulations, new red tape. But not new lines. Which is to say, posix at long last banishes new lines in file names.
Implementation seems straighforward, but like my mechanic says, it’s $1 knowing what to check and $99 knowing where to check.
POSIX doesn’t officially make a distinction about library functions or system calls, as far as I know. I think the closest hints would be thread cancellation points. We still have a variety of system calls to fix up, beyond the obvious open. Like openat, which is a distinct system call, but not creat which is just a libc wrapper. But in addition to rename and some others I’m probably forgetting, there’s connect with unix sockets.
So obviously don’t want to handle this at every entry point. Peaking at a system call like like sys_open redirects through vn_open, but a few others like sys_mkfifo reveal that all file name resolution in the kernel flows through namei in vfs_lookup.c. This is an ancient function from the days when inodes were the only thing you’d want to convert a name to. Some of its logic now lives in vfs_lookup which bears the comment “This is a very central and rather complicated routine.” So that’s how we know we’re looking in the right place.
It’s not so easy to summarize this function, which seems to consist of nothing but handling for special cases. There’s a lot of digging around with slashes and dots, but eventually we seem to find ourselves looking at the last component. And here we can start making changes. We only want to ban the creation of new forbidden names, not accessing existing files, so we have to allow directory lookups to work, and only fail create operations.
If my mechanic hasn’t lied to me, something like this.
/*
* We do special processing on the last component, whether or not it's
* a directory. Cache all intervening lookups, but not the final one.
*/
if (*cp == '\0') {
if (cnp->cn_nameiop == CREATE) {
for (const char *nl = cnp->cn_nameptr; *nl; nl++) {
if (*nl == '\n') {
error = EILSEQ;
goto bad;
}
}
}
I don’t really care too much about this change itself. There are still other characters awaiting misinterpretation. Is new line so much more dangerous than space? But I wanted to poke around to see where a check might go. Even knowing well of course it goes in namei leaves the question of where exactly.
Tagged: openbsd