| Summary: | open(O_CREAT|O_EXCL) erroneously resolves symlinks | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| Product: | [Yocto Project Subprojects] Pseudo | Reporter: | Simon Lindholm <simon.lindholm> | ||||||
| Component: | pseudo | Assignee: | Mark Hatle <mark.hatle> | ||||||
| Status: | RESOLVED FIXED | QA Contact: | |||||||
| Severity: | normal | ||||||||
| Priority: | Medium+ | CC: | hongxu.jia, mark.hatle, randy.macleod, yp.pseudo.watcher, yp.watcher | ||||||
| Version: | master | ||||||||
| Target Milestone: | 5.1 M3 | ||||||||
| Hardware: | x86 | ||||||||
| OS: | Multiple | ||||||||
| Whiteboard: | |||||||||
| OS type for building Yocto: | --- | Type of Regression: | --- | ||||||
| Verified: | Documentation change: | No (bug/feature does not impact docs) | |||||||
| Attachments: |
|
||||||||
Reviewed the proposed change. Consulting the open(2) man page, it indicates that O_EXCL behavior is 'undefined' unless used with O_CREAT. When both O_EXCL and O_CREAT are specified symbolic links are NOT followed. (O_NOFOLLOW behavior). There is an exception to block devices, but I don't believe that applies in this case. Additionally looking through the Linux 6.6.30 sources, I see that Linux assumes that O_CREAT|O_EXCL implies O_NOFOLLOW. (fs/open.c: build_open_flags(...)) So based on these two references, I think a fully "correct/complete" solution would set the flag if O_NOFOLLOW or (O_CREAT and O_EXCL) are set. But I'm not sure that sort of logic operation is possible in the wrappers. I'm still investigating this. Created attachment 5063 [details]
Revised patch
Patch was revised from original version to make the check more specific to how the Linux kernel handles the flags.
Revised patch has been sent to the yocto-patches list for inclusion. |
Created attachment 5060 [details] Patch for the bug, with some test cases. In Linux, in a call to open(pathname, O_CREAT|O_EXCL, ...), if pathname exists and is a symlink, pseudo will resolve the symlink instead immediately setting errno to EEXIST - which is what will happen when running without psuedo (i.e. O_CREAT|O_EXCL implies O_NOFOLLOW). For instance, consider the following snippet: $ cat -n pseudo_test.sh 1 #!/bin/sh -e 2 3 ln -s noexist.txt some_link 4 5 py_code=""" 6 import os 7 try: 8 os.open('some_link', os.O_RDWR | os.O_CREAT | os.O_EXCL) 9 print('Success') 10 except OSError as e: 11 print(os.strerror(e.errno)) 12 """ 13 14 echo -n "Without psuedo: " && python3 -c "$py_code" 15 echo -n "With psuedo: " && pseudo python3 -c "$py_code" $ ./pseudo_test.sh Without psuedo: File exists With psuedo: Success Issues from this could potentially manifest itself in various different ways, but this bug was discovered while investigating problems with a bitbake recipe that had a fakeroot-task in which it extracted a tar file containing symlinks. The first time the task was run it worked as expected, but when run again, tar ran into problems. When extracting files from a tar-archive, GNU tar will first try open(O_CREAT|O_EXCL), and if that doesn't succeed, check errno and if it's EEXIST it will remove the file, but if it gets e.g. ENOENT or EACCES (which would happen with some symlinks when they were resolved), then it will consider that an unrecoverable error. I'm attaching a patch to fix this bug (with some added test cases). The patch changes the flags for the different open calls (open, openat, open64, etc.) in ports/linux/wrapfuncs.in from flags=flags&O_NOFOLLOW to flags=flags&(O_NOFOLLOW|O_EXCL). In the generated pseudo_wrapfuncs.c the 'flags' from wrapfuncs.in are used for the 'leave_last' argument to pseudo_root_path(), i.e. by changing from just O_NOFOLLOW to O_NOFOLLOW|O_EXCL, 'leave_last' will be non-zero if either O_NOFOLLOW or O_EXCL is set (i.e. don't resolve symlink if O_EXCL is set). The patch is for master, but the bug is also present in release 1.9.0 (and probably earlier).