Skip to content

Commit

Permalink
Resolve falcosecurity#932, use btime from /proc/stat for boot time
Browse files Browse the repository at this point in the history
Get boot time from btime value in /proc/stat

ref: falcosecurity#932

/proc/uptime and btime in /proc/stat are fed by the same kernel sources.

Multiple ways to get boot time:
* btime in /proc/stat
* calculation via clock_gettime(CLOCK_REALTIME - CLOCK_BOOTTIME)
* calculation via time(NULL) - sysinfo().uptime

Maintainers preferred btime in /proc/stat because:
* value does not depend on calculation using current timestamp
* btime is "static" and doesn't change once set
* btime is available in kernels from 2008
* CLOCK_BOOTTIME is available in kernels from 2011 (2.6.38)

By scraping btime from /proc/stat, it is both the heaviest and most likely to succeed

Co-authored-by: Grzegorz Nosek <grzegorz.nosek@sysdig.com>
Co-authored-by: Melissa Kilby <melissa.kilby.oss@gmail.com>
Signed-off-by: Stanley Chan <pocketgamer5000@gmail.com>
  • Loading branch information
3 people committed Mar 27, 2023
1 parent 1db23eb commit b8d8f2a
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 39 deletions.
9 changes: 7 additions & 2 deletions userspace/libscap/linux/scap_procs.c
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,13 @@ int32_t scap_proc_fill_pidns_start_ts(char* error, struct scap_threadinfo* tinfo
char proc_cmdline_pidns[SCAP_MAX_PATH_SIZE];
struct stat targetstat = {0};

// Note: with this implementation, the "container start time" for host
// processes will not be equal to the boot time but to the time when the
// host init started.
snprintf(proc_cmdline_pidns, sizeof(proc_cmdline_pidns), "%sroot/proc/1/cmdline", procdirname);
if(stat(proc_cmdline_pidns, &targetstat) == 0)
{
tinfo->pidns_init_start_ts = targetstat.st_ctim.tv_sec * (uint64_t) 1000000000 + targetstat.st_ctim.tv_nsec;
tinfo->pidns_init_start_ts = targetstat.st_ctim.tv_sec * (uint64_t) SECOND_TO_NS + targetstat.st_ctim.tv_nsec;
return SCAP_SUCCESS;
}
else
Expand Down Expand Up @@ -977,11 +980,13 @@ static int32_t scap_proc_add_from_proc(scap_t* handle, uint32_t tid, char* procd
dir_name, handle->m_lasterr);
}

// Container start time for host processes will be equal to when the
// host init started
char proc_cmdline[SCAP_MAX_PATH_SIZE];
snprintf(proc_cmdline, sizeof(proc_cmdline), "%scmdline", dir_name);
if(stat(proc_cmdline, &dirstat) == 0)
{
tinfo->clone_ts = dirstat.st_ctim.tv_sec * (uint64_t) 1000000000 + dirstat.st_ctim.tv_nsec;
tinfo->clone_ts = dirstat.st_ctim.tv_sec * (uint64_t) SECOND_TO_NS + dirstat.st_ctim.tv_nsec;
}

// If tid is different from pid, assume this is a thread and that the FDs are shared, and set the
Expand Down
76 changes: 39 additions & 37 deletions userspace/libscap/scap.c
Original file line number Diff line number Diff line change
Expand Up @@ -1235,53 +1235,55 @@ uint64_t scap_get_driver_schema_version(scap_t* handle)

int32_t scap_get_boot_time(char* last_err, uint64_t *boot_time)
{
#ifdef __linux__
struct timespec ts_uptime = {0};
struct timespec tv_now = {0};
uint64_t now = 0;
uint64_t uptime = 0;
char proc_cmdline[PPM_MAX_PATH_SIZE];
struct stat targetstat = {0};

/* More reliable way to get boot time, similar to Docker */
snprintf(proc_cmdline, sizeof(proc_cmdline), "%s/proc/1/cmdline", scap_get_host_root());
if (stat(proc_cmdline, &targetstat) == 0)
{
/* This approach is constant between agent re-boots */
*boot_time = targetstat.st_ctim.tv_sec * (uint64_t) SECOND_TO_NS + targetstat.st_ctim.tv_nsec;
return SCAP_SUCCESS;
}
*boot_time = 0;

/*
* Fall-back method
#ifdef __linux__
uint64_t btime = 0;
char proc_stat[PPM_MAX_PATH_SIZE];
char line[512];

/* Get boot time from btime value in /proc/stat
* ref: https://github.com/falcosecurity/libs/issues/932
* /proc/uptime and btime in /proc/stat are fed by the same kernel sources.
*
* Multiple ways to get boot time:
* btime in /proc/stat
* calculation via clock_gettime(CLOCK_REALTIME - CLOCK_BOOTTIME)
* calculation via time(NULL) - sysinfo().uptime
*
* Maintainers preferred btime in /proc/stat because:
* value does not depend on calculation using current timestamp
* btime is "static" and doesn't change once set
* btime is available in kernels from 2008
* CLOCK_BOOTTIME is available in kernels from 2011 (2.6.38
*
* By scraping btime from /proc/stat,
* it is both the heaviest and most likely to succeed
*/

/* Get the actual time */
if(clock_gettime(CLOCK_REALTIME, &tv_now))
snprintf(proc_stat, sizeof(proc_stat), "%s/proc/stat", scap_get_host_root());
FILE* f = fopen(proc_stat, "r");
if (f == NULL)
{
if(last_err != NULL)
{
snprintf(last_err, SCAP_LASTERR_SIZE, "clock_gettime(): unable to get the 'CLOCK_REALTIME'");
}
ASSERT(false);
return SCAP_FAILURE;
}
now = tv_now.tv_sec * (uint64_t)SECOND_TO_NS + tv_now.tv_nsec;

/* Get the uptime since the boot */
if(clock_gettime(CLOCK_BOOTTIME, &ts_uptime))
while(fgets(line, sizeof(line), f) != NULL)
{
if(last_err != NULL)
if(sscanf(line, "btime %" PRIu64, &btime) == 1)
{
snprintf(last_err, SCAP_LASTERR_SIZE, "clock_gettime(): unable to get the 'CLOCK_BOOTTIME'");
fclose(f);
*boot_time = btime * (uint64_t) SECOND_TO_NS;
return SCAP_SUCCESS;
}
else
{
fclose(f);
ASSERT(false);
return SCAP_FAILURE;
}
return SCAP_FAILURE;
}
uptime = ts_uptime.tv_sec * (uint64_t)SECOND_TO_NS + ts_uptime.tv_nsec;

/* Compute the boot time as the difference between actual time and the uptime. */
*boot_time = now - uptime;
#else
*boot_time = 0;
fclose(f);
#endif
return SCAP_SUCCESS;
}

0 comments on commit b8d8f2a

Please sign in to comment.