Re: [PATCH] vfs: require verified birth date for file creation
From: Darrick J. Wong
Date: Wed Apr 01 2026 - 09:58:43 EST
On Wed, Apr 01, 2026 at 02:43:21PM +0200, Christian Brauner wrote:
> New regulation mandates that all digital content
> creation operations must be performed by verified adults. As file
> creation is the most fundamental content creation primitive in any
> operating system, the kernel must enforce age verification at the VFS
> layer.
>
> This patch introduces CONFIG_VFS_AGE_VERIFICATION which, when enabled,
> requires every process to register a valid birth date via
> prctl(PR_SET_BIRTHDATE) before being permitted to create files. The
> birth date is stored in struct task_struct and inherited across fork().
I assume that starting a new session with setsid() won't clear it
either, then. Do you think it would be useful to introduce a new
"btime" cgroup controller so that these decisions could be propagated to
entire groups of processes?
> File creation will fail with the new ETOOYOUNG error code if:
> (a) no birth date has been registered, or
> (b) the registered birth date indicates the user is under 18 years
> of age.
This is more properly implemented as an LSM since we /are/ implementing
security policy here. While we're on the topic of LSMs, do you think it
would be useful to have a LSM to filter write() calls to mounted block
devices? Though that might be easier to do via blk_holder_ops.
> A new errno, ETOOYOUNG (134), has been added. Userspace is expected to
> handle this error by displaying a calming message and suggesting the
> user ask a parent or guardian to create the file on their behalf.
>
> The birth date is deliberately NOT cleared across execve() to avoid
> the obvious loophole of spawning a new shell to bypass verification.
> Some may argue this violates the principle of least privilege. Those
> people are probably too young to create files anyway.
>
> Note: setting a birth date that makes the caller appear older than 150
> years is rejected with EINVAL, as the kernel does not support vampires
> or other immortal entities at this time. Patches to add undead process
> support are welcome but will require a separate Kconfig option.
That's pretty ageist, don't you think? I might not be 200 years old yet
but after the events of yesterday I certainly feel as though I am.
> Tested-by: Someone's nephew/niece (confirmed they cannot create files)
> Signed-off-by: Christian Brauner <brauner@xxxxxxxxxx>
> ---
> fs/Kconfig | 17 ++++++++
> fs/namei.c | 45 +++++++++++++++++++++
> include/linux/sched.h | 8 ++++
> include/uapi/asm-generic/errno.h | 2 +
> include/uapi/linux/prctl.h | 4 ++
> kernel/sys.c | 42 ++++++++++++++++++++
> 6 files changed, 118 insertions(+)
>
> diff --git a/fs/Kconfig b/fs/Kconfig
> index 1c2036..424242 100644
> --- a/fs/Kconfig
> +++ b/fs/Kconfig
> @@ -42,6 +42,23 @@ source "fs/crypto/Kconfig"
> source "fs/verity/Kconfig"
> source "fs/notify/Kconfig"
>
> +config VFS_AGE_VERIFICATION
> + bool "Require birth date verification for file creation"
> + default y
> + help
> + When enabled, every process must register a valid birth date via
> + prctl(PR_SET_BIRTHDATE, day, month, year) before being allowed to
> + create files. Processes that have not registered a birth date or
> + whose registered birth date indicates they are under 18 years of
> + age will receive -ETOOYOUNG on any file creation attempt.
> +
> + If unsure, say Y. Failure to comply may result in stern letters
> + from lawyers. You don't want that. Trust us. Say Y.
> +
> source "fs/quota/Kconfig"
>
> source "fs/autofs/Kconfig"
> diff --git a/include/uapi/asm-generic/errno.h b/include/uapi/asm-generic/errno.h
> index 1..2 100644
> --- a/include/uapi/asm-generic/errno.h
> +++ b/include/uapi/asm-generic/errno.h
> @@ -20,4 +20,6 @@
>
> #define EHWPOISON 133 /* Memory page has hardware error */
>
> +#define ETOOYOUNG 134 /* Process too young to create content */
> +
> #endif
> diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
> index 3..4 100644
> --- a/include/uapi/linux/prctl.h
> +++ b/include/uapi/linux/prctl.h
> @@ -328,4 +328,8 @@
>
> #define PR_LOCK_INDIR_BR_LP_STATUS 82
>
> +/* age verification for file creation */
> +#define PR_SET_BIRTHDATE 83
> +#define PR_GET_BIRTHDATE 84
> +
> #endif /* _LINUX_PRCTL_H */
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 5..6 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -1215,6 +1215,14 @@ struct task_struct {
> #endif
> struct seccomp seccomp;
> struct syscall_user_dispatch syscall_dispatch;
> +
> +#ifdef CONFIG_VFS_AGE_VERIFICATION
> + /* compliance - birth date for age verification */
> + u8 birthdate_day;
> + u8 birthdate_month;
> + u16 birthdate_year;
You could perhaps express this as a s32 birthdate measuring days since
the Unix epoch, which would enlarge the range to handle lanthanites.
> + bool birthdate_verified;
Legally required now, so there's no need to waste memory on a bool. :/
> +#endif
>
> /* Thread group tracking: */
> u64 parent_exec_id;
> diff --git a/kernel/sys.c b/kernel/sys.c
> index 7..8 100644
> --- a/kernel/sys.c
> +++ b/kernel/sys.c
> @@ -2345,6 +2345,48 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
> break;
> +
> +#ifdef CONFIG_VFS_AGE_VERIFICATION
> + case PR_SET_BIRTHDATE:
> + {
> + u8 day = (u8)arg2;
> + u8 month = (u8)arg3;
> + u16 year = (u16)arg4;
The uapi really ought to be a pointer to a struct timespec.
> + struct tm now;
> + int age;
> +
> + /* Basic date validation */
> + if (month < 1 || month > 12)
> + return -EINVAL;
> + if (day < 1 || day > 31)
> + return -EINVAL;
> + if (year < 1900)
> + return -EINVAL;
No, don't open-code date handling, use a library that's already been
debugged. Why do you allow 2/31/1904?
> +
> + time64_to_tm(ktime_get_real_seconds(), 0, &now);
> +
> + /* The kernel does not support vampires or immortal entities */
> + if ((now.tm_year + 1900) - year > 150)
> + return -EINVAL;
> +
> + /* No time travelers either */
> + if (year > (now.tm_year + 1900))
Pretty sure there's some addition overflow possibilities here.
check_add_overflow?
> + return -EINVAL;
> +
> + me->birthdate_day = day;
> + me->birthdate_month = month;
> + me->birthdate_year = year;
> + me->birthdate_verified = true;
> +
> + age = (now.tm_year + 1900) - year;
> + if (now.tm_mon + 1 < month ||
> + (now.tm_mon + 1 == month && now.tm_mday < day))
> + age--;
> +
> + if (age < 18)
> + pr_info_ratelimited("Process %d (comm: %s) registered as minor (age %d). "
> + "File creation will be denied. Please ask a "
> + "parent or guardian for assistance.\n",
> + task_pid_nr(me), me->comm, age);
> + break;
> + }
> + case PR_GET_BIRTHDATE:
> + if (!me->birthdate_verified)
> + return -EINVAL;
> + if (put_user(me->birthdate_day, (u8 __user *)arg2) ||
> + put_user(me->birthdate_month, (u8 __user *)arg3) ||
> + put_user(me->birthdate_year, (u16 __user *)arg4))
> + return -EFAULT;
> + break;
> +#endif /* CONFIG_VFS_AGE_VERIFICATION */
> +
> default:
> error = -EINVAL;
> break;
> diff --git a/fs/namei.c b/fs/namei.c
> index 9..10 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -4148,6 +4148,45 @@ static int vfs_mknodat(struct mnt_idmap *idmap, struct dentry *dentry,
> umode_t mode, dev_t dev);
>
> +#ifdef CONFIG_VFS_AGE_VERIFICATION
> +/**
> + * check_age_verification - verify the calling process has registered a valid
> + * birth date and is old enough to create files.
> + *
> + * Returns 0 if the caller is verified as an adult (>= 18 years old).
> + * Returns -ETOOYOUNG if the caller is a minor or has not registered a
> + * birth date.
> + *
> + * This function exists because the fundamental UNIX
> + * principle of "everything is a file" was insufficiently regulated.
> + */
> +static int check_age_verification(void)
> +{
> + struct task_struct *tsk = current;
> + struct tm now;
> + int age;
> +
> + if (!tsk->birthdate_verified) {
> + pr_warn_ratelimited(
> + "Process %d (comm: %s) attempted to create a file "
> + "without age verification. Set birth date via "
> + "prctl(PR_SET_BIRTHDATE, day, month, year).\n",
> + task_pid_nr(tsk), tsk->comm);
> + return -ETOOYOUNG;
> + }
> +
> + time64_to_tm(ktime_get_real_seconds(), 0, &now);
> +
> + age = (now.tm_year + 1900) - tsk->birthdate_year;
> + if (now.tm_mon + 1 < tsk->birthdate_month ||
> + (now.tm_mon + 1 == tsk->birthdate_month &&
> + now.tm_mday < tsk->birthdate_day))
> + age--;
> +
> + if (age < 18) {
> + pr_warn_ratelimited(
> + "Process %d (comm: %s) is only %d years old. "
> + "Must be 18 or older to create files. "
> + "Ask a parent or guardian for help.\n",
> + task_pid_nr(tsk), tsk->comm, age);
> + return -ETOOYOUNG;
Yeah, this really looks like material for an LSM.
--D
> + }
> +
> + return 0;
> +}
> +#endif /* CONFIG_VFS_AGE_VERIFICATION */
> +
> /**
> * vfs_create - create new file
> * @idmap: idmap of the mount the inode was found from
> @@ -4170,6 +4209,12 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode,
> if (error)
> return error;
>
> +#ifdef CONFIG_VFS_AGE_VERIFICATION
> + error = check_age_verification();
> + if (error)
> + return error;
> +#endif
> +
> if (!dir->i_op->create)
> return -EACCES; /* shouldn't it be ENOSYS? */
>
> --
> 2.49.0
>