[PATCH] misc: Add mkostempat (BZ 19866)
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Mon Jun 22 13:57:29 GMT 2026
On 19/06/26 08:25, Mark Wielaard wrote:
> Hi Adhemerval,
>
> On Thu, 2026-06-18 at 16:11 -0300, Adhemerval Zanella wrote:
>> The mkstemp family has a lot of potential pitfalls:
>>
>> 1. In-place mutation of the template, where makes string literals
>> usage not possible.
>> 2. The randomness count is baked in the string and it can not be
>> changed (users can no ask for more).
>> 3. O_CLOEXEC is not the default.
>> 4. A lot of combinatorial variant: mkstemp -> mkostemp (flags) ->
>> mkstemps (suffix) -> mkostemps (both)
>> 5. No mode parameter, the file is always created as 0600.
>> 6. Suffix typing and positioning: the XXXXXX must sit at exactly
>> strlen(template) - suffixlen - 6 and mismatches are setups for
>> EINVAL rather than compile-time errors.
>> 7. mkostemp accepts only O_APPEND/O_CLOEXEC/O_SYNC(-ish); anything
>> else is rejected, but the signature gives no hint.
>>
>> This patch adds mkostempat:
>>
>> int mkostempat (int dirfd, /* as other *at functions. */
>> const char *prefix, /* maybe a relative or absolute. */
>> const char *suffix, /* no '/' allowed; may be "" */
>> unsigned int n_random, /* entropy chars, 0 -> default 6 */
>> int oflags, /* extra O_* OR'd into the create */
>> mode_t mode, /* e.g. 0600 */
>> char *namebuf, /* output, or NULL */
>> size_t namebuf_size); /* bound; ERANGE/ENAMETOOLONG on overflow */
>>
>> The functions is a GNU extension and a more flexible alternative to the
>> mkstemp family: it creates the file relative to a directory descriptor
>> (with openat's absolute-path/dirfd semantics)i. It also fixes the
>> aforementioned issues:
>>
>> * dirfd first, AT_FDCWD semantics (as other *at functions).
>> * namebuf/namebuf_size replaces the mutated template, killing the
>> string-literal footgun and the lifetime coupling. NULL namebuf means
>> "I only want the fd" (the common O_TMPFILE-adjacent case)
>> * prefix+suffix+n_random replaces the XXXXXX/suffixlen convention.
>> * O_CLOEXEC as the implicit default, with oflags for opt-out/extra flags.
>> * mode so 0600 is a default the caller can override atomically.
>
> When I mentioned it in
> https://sourceware.org/bugzilla/show_bug.cgi?id=19866 I mostly wanted
> an alternative for mkstemp that took a dirfd to go with the other at
> calls to replace path based calls with dirfd calls to get rid of
> various toctou issues with the path based calls. I appreciate the way
> more flexible mkostempat but am a little afraid it becomes so
> complicated that people won't have an easy transition to a more safe
> api.
Fair enough. I designed this interface to be a superset of the mkstemp
family, similar on how new Linux syscalls are done. The idea is callers
could replace mkstemp calls with mkostempat, but maybe it would be better
to provide a more strict and safe interface.
I think the changes below actually pull it back toward simplicity rather
than away from it: the parameter count drops from 8 to 7, the size/ERANGE
bookkeeping is gone, and the path handling is removed.
The trivial "just give me a temp fd" case is now:
int fd = mkostempat (dirfd, NULL, NULL, 0, 0, 0600, NULL);
And the "I also want the name" case:
char *name;
int fd = mkostempat (dirfd, NULL, NULL, 0, 0, 0600, &name);
...
free (name);
>
> That said, I do like this. The dirfd argument, O_CLOEXEC and just
> letting glibc decide what randomness/template is needed are clear
> improvements.
>
> If possible it would be nice to have some default for the prefix. Does
> the prefix really matter? Maybe allow NULL for both prefix and suffix?
Make sense, I changed to make prefix and suffix both accept NULL and
default to the empty string, so the prefix is entirely optional. I kept them
as separate parameters rather than inventing a fixed default string, a NULL
prefix already provides a random characters name.
>
> I understand that other at functions do allow (absolute) paths (slash
> '/' chars), but that really just brings back the path based toctou
> issues, so I would advise against it (although I see how there is a
> consistency argument to be made wrt other at calls).
Fair enough: a / in either prefix or suffix now fails with EINVAL. The
generated name is always a single component created directly in dirfd,
so there is no intermediate path walk and then no TOCTOU window.
>
> Could we make the namebuf pure output (that you have to free when
> done)? If not, how does the user determine the namebuf_size?
>
> When creating an O_TMPFILE I think the user still wants a path name to
> use either when the file system didn't support O_TMPFILE so you had to
> fall back to a real filename, or when used to (atomically) replace
> another file (which I believe is a common use case for mkstemp) because
> there is no flink that just takes an fd and a new (or to be replaced)
> name. renameat still needs a pathname (maybe /proc/self/fd/nr?).
I think it is doable, but atomic-replace is still clunky for O_TMPFILE.
With O_TMPFILE set, mkostempat can creates the unnamed file directly in
dirfd and reports it by setting *namebuf to NULL. The descriptor is
anonymous and returning /proc/self/fd/N has multiples issues - procfs
might not be accessible and it goes stale after dup (so I think it should
not be returned).
However, the atomic-replace still requires either:
1. linkat (fd, "", newdirfd, newname, AT_EMPTY_PATH) — the closest thing to an
"flink." But per the man page it requires CAP_DAC_READ_SEARCH (privileged).
2. linkat (AT_FDCWD, "/proc/self/fd/<fd>", newdirfd, newname, AT_SYMLINK_FOLLOW)
— the unprivileged workaround, but needs procfs mounted.
So the only way to atomically replace in a generic way using an O_TMPFILE fd is
the two-step dance: linkat it to a fresh temp name (retrying on EEXIST, which is
what mkstemp does...), then renameat(temp, target).
With mkostempat would be something like:
char *tmp;
int fd = mkostempat (dirfd, ".target-", NULL, 0, 0, 0600, &tmp);
write (fd, data, len); fsync (fd);
renameat (dirfd, tmp, dirfd, "target"); /* atomic replace */
free (tmp);
The O_TMPFILE one less malloc/free, but it requires procfs to be mounted *and* an
unique tmpname. I am more inclined to make O_TMPFILE return EINVAL instead.
I am working a v2 based on this design, I am still not convinced on adding
the callback-like interface to allow openat2.
>
> Cheers,
>
> Mark
More information about the Libc-alpha
mailing list