# About Name: Perj Description: You can add a great description here to make the blog readers visit your landing page. URL: https://perj.com/blog # Navigation Menu - Home: https://perj.superblog.click/ - Sample Page: https://perj.superblog.click/sample-page - Search: https://perj.superblog.click/search - Sample CTA Button: https://www.myswitzerland.com/en-gb/destinations/lauterbrunnen/ # Blog Posts ## Linux syscalls open, openat and openat2 Author: Markus De Shon Author URL: https://perj.com/blog/author/markus-de-shon Published: 2026-07-17 URL: https://perj.com/blog/linux-syscalls-open-openat-and-openat2 On Linux, when you are collecting filesystem events using bpftrace (which uses eBPF), you may get an error something like this: ```plaintext ERROR: No matches for tracepoint syscalls:sys_exit_open ``` But open() is a very common system call, so how can this be? Let’s look at the available open syscall tracepoints for when they are called (there's a corresponding exit tracepoints for each that gives the return values). ```plaintext # grep -i 'sys_enter_*open*' /sys/kernel/tracing/available_events syscalls:sys_enter_openat2 syscalls:sys_enter_openat syscalls:sys_enter_open_tree syscalls:sys_enter_open_by_handle_at ``` Where is sys\_enter\_open? Spoiler: it’s not there because on ARM64 (aarch64) architecture, open() is silently mapped to openat(). open() is still present on x86\_64 systems so the tracepoint also exists. I think it’s worthwhile going through the history of the various open\* calls, why they were added, and why there is no open() syscall on aarch64. There’s a lot to learn along the way about how Linux handles files. ## In the beginning, there was open() Let’s look at how Linux kernel 1.0 (March 1994) implemented the open() call, mainly because the code is simple and illustrates the main problem with open (thank you to kernel.org for continuing to host all the historical versions): ```c asmlinkage int sys_open(const char * filename,int flags,int mode) { char * tmp; int error; error = getname(filename, &tmp); if (error) return error; error = do_open(tmp,flags,mode); putname(tmp); return error; } ``` So, it copies the supplied file path into the tmp location, then calls the do\_open() function to do the actual work. Here is do\_open: ```c int do_open(const char * filename,int flags,int mode) { struct inode * inode; struct file * f; int flag,error,fd; for(fd=0 ; fdfilp[fd]) break; if (fd>=NR_OPEN) return -EMFILE; FD_CLR(fd,¤t->close_on_exec); f = get_empty_filp(); if (!f) return -ENFILE; current->filp[fd] = f; f->f_flags = flag = flags; f->f_mode = (flag+1) & O_ACCMODE; if (f->f_mode) flag++; if (flag & (O_TRUNC | O_CREAT)) flag |= 2; error = open_namei(filename,flag,mode,&inode,NULL); if (error) { current->filp[fd]=NULL; f->f_count--; return error; } f->f_inode = inode; f->f_pos = 0; f->f_reada = 0; f->f_op = NULL; if (inode->i_op) f->f_op = inode->i_op->default_file_ops; if (f->f_op && f->f_op->open) { error = f->f_op->open(inode,f); if (error) { iput(inode); f->f_count--; current->filp[fd]=NULL; return error; } } f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); return (fd); } ``` This was single-CPU, single-core code in those days, so only one task would be executing at a time, and its context would be represented by current, which is a pointer to a task\_struct. If the task sleeps, the context is saved and current is pointed back to it when the task revives. So, some things in this code that could be race conditions in multi-processor situations are not. We’ll ignore some other problems with this very early kernel code that aren’t relevant to this discussion. There is one important race condition here that persisted for a long time: the call to open\_namei() does some checks that are important for security purposes, and returns an error if the checks fail, for example: ```c if (inode == (*p)->executable) { iput(inode); return -ETXTBSY; } ``` Which returns a “text file is busy” error if you’re trying to modify an executable file that’s currently running (confusing error message, but it made sense to the authors at the time). Then later, back in the do\_open function, the actual open happens. ```c f->f_op->open(inode,f); ``` This is called a “Time of check to time of use” (TOCTOU) race condition, and it can lead to security vulnerabilities. An example is CVE-2024-43882 which affects the exec.c call, but the core problem was the same: a permission check happened before the file execution itself (without locking the file first), so an attacker had the opportunity to swap out the benign file with a malicious one before the setuid bit was applied. The fix was to do the permission check and execution inside an inode\_lock/inode\_unlock block so that it’s atomic. ## How was open() fixed? The race condition was recognized a long time ago, with a fix implemented in kernel version 2.6.16 (March 2006), by supplying a new function called openat(). Both sys\_open and sys\_openat still called the do\_open function, and the guts of the open operations was in the open\_namei() function, where critical check/use pairs were enclosed in mutex lock blocks: ```c mutex_lock(&dir->d_inode->i_mutex); // check here // use here mutex_unlock(&dir->d_inode->i_mutex); ``` This removes the race condition because no one else can modify the file while it's locked. So by 2.6.16 we have two syscalls, open() and openat(), both of which are safe. This was pre-eBPF times, so there were no corresponding eBPF trace endpoints. ## When was ARM64 support added? ARM64 support entered the kernel starting with Linux 3.7 (December 2012), with an application binary interface (ABI) that included openat(), but not open(). The idea was that open() was already deprecated, so in glibc open() on aarch64 just calls openat() underneath. ## When was eBPF added? Linux 4.1 (June 2015) added the ability to do eBPF using kprobes, but Linux 4.7 (July 2016) added the ability to attach eBPF directly to tracepoints supplied by the kernel. It’s at this point that ```plaintext syscalls:sys_enter_openat syscalls:sys_enter_open ``` became available on x86\_64 systems. For aarch64, since open() was not actually a syscall there was only the tracepoint syscalls:sys\_enter\_openat from Linux 4.7 until openat2() was added. On aarch64 any call to open() would show up in eBPF as an openat() call instead. ## When was openat2() added? Linux 5.6 (March 2020) added the openat2() call, and changed do\_sys\_open() into a wrapper for do\_sys\_openat2(), by preparing the open\_how struct using a build\_open\_how() function. ```c struct open_how { __u64 flags; __u64 mode; __u64 resolve; }; inline struct open_how build_open_how(int flags, umode_t mode) { struct open_how how = { .flags = flags & VALID_OPEN_FLAGS, .mode = mode & S_IALLUGO, }; /* O_PATH beats everything else. */ if (how.flags & O_PATH) how.flags &= O_PATH_FLAGS; /* Modes should only be set for create-like flags. */ if (!WILL_CREATE(how.flags)) how.mode = 0; return how; } ``` openat2() added the resolve flags to define how components of the path are resolved. For example, RESOLVE\_IN\_ROOT ensures that the directory referred to by dirfd is the root, and only files and directories below that root can be accessed. These kinds of options substantially enhance security, and deserve a blog post of their own. By this time, part of the SYSCALL\_DEFINE\* macros was to add a tracepoint for the syscall as it was being defined. So the tracepoint for syscall\_enter\_openat2 also appeared at this time. So, starting with Linux 5.6 we see ```plaintext syscalls:sys_enter_openat2 syscalls:sys_enter_openat syscalls:sys_enter_open // not on aarch64 ``` and that’s still the situation today (July 2026). --- This blog is powered by Superblog. Visit https://superblog.ai to know more. --- ## Sample Page Author: Markus De Shon Author URL: https://perj.com/blog/author/markus-de-shon Published: 2026-07-17 URL: https://perj.com/blog/sample-page This is a page. Notice how there are no elements like author, date, social sharing icons? Yes, this is the page format. You can create a whole website using Superblog if you wish to do so! --- This blog is powered by Superblog. Visit https://superblog.ai to know more. ---