| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Author: Andrei Vagin <avagin@openvz.org> | |
| * Author: Dmitry Safonov <dima@arista.com> | |
| */ | |
| #include <linux/cleanup.h> | |
| #include <linux/mm.h> | |
| #include <linux/time_namespace.h> | |
| #include <linux/time.h> | |
| #include <linux/vdso_datastore.h> | |
| #include <vdso/clocksource.h> | |
| #include <vdso/datapage.h> | |
| #include "namespace_internal.h" | |
| static struct timens_offset offset_from_ts(struct timespec64 off) | |
| { | |
| struct timens_offset ret; | |
| ret.sec = off.tv_sec; | |
| ret.nsec = off.tv_nsec; | |
| return ret; | |
| } | |
| /* | |
| * A time namespace VVAR page has the same layout as the VVAR page which | |
| * contains the system wide VDSO data. | |
| * | |
| * For a normal task the VVAR pages are installed in the normal ordering: | |
| * VVAR | |
| * PVCLOCK | |
| * HVCLOCK | |
| * TIMENS <- Not really required | |
| * | |
| * Now for a timens task the pages are installed in the following order: | |
| * TIMENS | |
| * PVCLOCK | |
| * HVCLOCK | |
| * VVAR | |
| * | |
| * The check for vdso_clock->clock_mode is in the unlikely path of | |
| * the seq begin magic. So for the non-timens case most of the time | |
| * 'seq' is even, so the branch is not taken. | |
| * | |
| * If 'seq' is odd, i.e. a concurrent update is in progress, the extra check | |
| * for vdso_clock->clock_mode is a non-issue. The task is spin waiting for the | |
| * update to finish and for 'seq' to become even anyway. | |
| * | |
| * Timens page has vdso_clock->clock_mode set to VDSO_CLOCKMODE_TIMENS which | |
| * enforces the time namespace handling path. | |
| */ | |
| static void timens_setup_vdso_clock_data(struct vdso_clock *vc, | |
| struct time_namespace *ns) | |
| { | |
| struct timens_offset *offset = vc->offset; | |
| struct timens_offset monotonic = offset_from_ts(ns->offsets.monotonic); | |
| struct timens_offset boottime = offset_from_ts(ns->offsets.boottime); | |
| vc->seq = 1; | |
| vc->clock_mode = VDSO_CLOCKMODE_TIMENS; | |
| offset[CLOCK_MONOTONIC] = monotonic; | |
| offset[CLOCK_MONOTONIC_RAW] = monotonic; | |
| offset[CLOCK_MONOTONIC_COARSE] = monotonic; | |
| offset[CLOCK_BOOTTIME] = boottime; | |
| offset[CLOCK_BOOTTIME_ALARM] = boottime; | |
| } | |
| struct page *find_timens_vvar_page(struct vm_area_struct *vma) | |
| { | |
| if (likely(vma->vm_mm == current->mm)) | |
| return current->nsproxy->time_ns->vvar_page; | |
| /* | |
| * vvar_fault() protects this from being called through remote interfaces like | |
| * /proc/$pid/mem or process_vm_{readv,writev}(). | |
| */ | |
| WARN(1, "vvar_page accessed remotely"); | |
| return NULL; | |
| } | |
| static void timens_set_vvar_page(struct task_struct *task, | |
| struct time_namespace *ns) | |
| { | |
| struct vdso_time_data *vdata; | |
| struct vdso_clock *vc; | |
| unsigned int i; | |
| if (ns == &init_time_ns) | |
| return; | |
| /* Fast-path, taken by every task in namespace except the first. */ | |
| if (likely(ns->frozen_offsets)) | |
| return; | |
| guard(mutex)(&timens_offset_lock); | |
| /* Nothing to-do: vvar_page has been already initialized. */ | |
| if (ns->frozen_offsets) | |
| return; | |
| ns->frozen_offsets = true; | |
| vdata = page_address(ns->vvar_page); | |
| vc = vdata->clock_data; | |
| for (i = 0; i < CS_BASES; i++) | |
| timens_setup_vdso_clock_data(&vc[i], ns); | |
| if (IS_ENABLED(CONFIG_POSIX_AUX_CLOCKS)) { | |
| for (i = 0; i < ARRAY_SIZE(vdata->aux_clock_data); i++) | |
| timens_setup_vdso_clock_data(&vdata->aux_clock_data[i], ns); | |
| } | |
| } | |
| /* | |
| * The vvar page layout depends on whether a task belongs to the root or | |
| * non-root time namespace. Whenever a task changes its namespace, the VVAR | |
| * page tables are cleared and then they will be re-faulted with a | |
| * corresponding layout. | |
| * See also the comment near timens_setup_vdso_clock_data() for details. | |
| */ | |
| static int vdso_join_timens(struct task_struct *task, struct time_namespace *ns) | |
| { | |
| struct mm_struct *mm = task->mm; | |
| struct vm_area_struct *vma; | |
| VMA_ITERATOR(vmi, mm, 0); | |
| guard(mmap_read_lock)(mm); | |
| for_each_vma(vmi, vma) { | |
| if (vma_is_special_mapping(vma, &vdso_vvar_mapping)) | |
| zap_vma(vma); | |
| } | |
| return 0; | |
| } | |
| void timens_commit(struct task_struct *tsk, struct time_namespace *ns) | |
| { | |
| timens_set_vvar_page(tsk, ns); | |
| vdso_join_timens(tsk, ns); | |
| } | |
| int timens_vdso_alloc_vvar_page(struct time_namespace *ns) | |
| { | |
| ns->vvar_page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO); | |
| if (!ns->vvar_page) | |
| return -ENOMEM; | |
| return 0; | |
| } | |
| void timens_vdso_free_vvar_page(struct time_namespace *ns) | |
| { | |
| __free_page(ns->vvar_page); | |
| } |