Hi,
One problem with audio systems that
a) runs as RT processes
b) allows user loaded plugins
Is that it is very simple to make a DOS (Denial of service) attack.
This has resulted in that the feature to run arts in RT mode has been
disabled... see artswrapper.c, search for NO_MORE_LOCAL_DOS_HOLE
I made this code as a proof of concept for a monitor that catches and
reduces priority for processes that misuse this feature.
(this version reduces the priority for all RT processes at once...)
I have been away for some time, so I do not know what happened to it.
But I see that the RT possibility is still removed from arts (Stefan?)
But I figured out that those of you that develops RT plugins might find
it useful even in this form.
Note:
* it has not been tested on SMP (it is likely to be buggy...)
* if your runaway RT program runs on highest SCHED_FIFO priority
this monitor can not help you.
/RogerL
--
Roger Larsson
Skellefte�
Sweden
/* RT monitor.
Copyright (c) 2002 Roger Larsson <[EMAIL PROTECTED]>
This program is free software; you can redistribute it and/or
modify it under the terms of version 2 of the GNU General Public
License as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Thanks to autor of KSysGuard Chris Schlaeger for borrowed code...
*/
#include <sys/types.h>
#include <sched.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/mman.h>
int isRT(pid_t pid)
{
int sched_class = sched_getscheduler( pid);
if (sched_class == -1) {
fprintf(stderr, "Pid %d Exited?\n", pid);
return 0;
}
return sched_class != SCHED_OTHER;
}
struct rt_process_info
{
/* This flag is set for all found processes at the beginning of the
* process list update. Processes that do not have this flag set will
* be assumed dead and removed from the list. The flag is cleared after
* each list update. */
int alive;
int centStamp;
pid_t pid;
pid_t ppid;
gid_t gid;
unsigned int userTime;
unsigned int sysTime;
unsigned int vmSize; // enough?
unsigned int vmRss; // enough?
float sysLoad;
float userLoad;
float cpu_usage;
};
#define MAX_RT_PROCESSES 200
struct rt_process_info rt_process[MAX_RT_PROCESSES]; /* pid & alive == 0 */
struct rt_process_info *find_process(pid_t pid)
{
unsigned ix;
for (ix = 0; ix < MAX_RT_PROCESSES; ix++)
{
if (rt_process[ix].pid == pid) {
rt_process[ix].alive = 1;
return &rt_process[ix];
}
}
return NULL;
}
struct rt_process_info *new_process(pid_t pid)
{
unsigned ix;
for (ix = 0; ix < MAX_RT_PROCESSES; ix++)
{
if (rt_process[ix].pid == 0) {
rt_process[ix].pid = pid;
rt_process[ix].alive = 2;
return &rt_process[ix];
}
}
return NULL;
}
float cpu_usage(struct rt_process_info *ps)
{
#define BUFSIZE 1024
char buf[BUFSIZE];
FILE *fd;
char status;
unsigned int userTime, sysTime;
snprintf(buf, BUFSIZE - 1, "/proc/%d/stat", ps->pid);
buf[BUFSIZE - 1] = '\0';
if ((fd = fopen(buf, "r")) == 0)
return (-1);
if (fscanf(fd, "%*d %*s %c %d %d %*d %*d %*d %*u %*u %*u %*u %*u %d %d"
"%*d %*d %*d %*d %*u %*u %*d %u %u",
&status, (int*) &ps->ppid, (int*) &ps->gid,
&userTime, &sysTime, &ps->vmSize,
&ps->vmRss) != 7) {
fclose(fd);
return (-1);
}
if (fclose(fd))
return (-1);
{
unsigned int newCentStamp;
int timeDiff, userDiff, sysDiff;
struct timeval tv;
gettimeofday(&tv, 0);
newCentStamp = tv.tv_sec * 100 + tv.tv_usec / 10000;
// calculate load
if (ps->alive == 2)
ps->sysLoad = ps->userLoad = 0.0f; /* can't give relieable number at the moment... */
else {
timeDiff = (int)(newCentStamp - ps->centStamp);
userDiff = userTime - ps->userTime;
sysDiff = sysTime - ps->sysTime;
if ((timeDiff > 0) && (userDiff >= 0) && (sysDiff >= 0)) /* protect from bad data */
{
ps->userLoad = ((double) userDiff / timeDiff) * 100.0;
ps->sysLoad = ((double) sysDiff / timeDiff) * 100.0;
}
else
ps->sysLoad = ps->userLoad = 0.0;
}
// update fields
ps->centStamp = newCentStamp;
ps->userTime = userTime;
ps->sysTime = sysTime;
}
ps->cpu_usage = ps->userLoad + ps->sysLoad;
return ps->cpu_usage;
}
/* process reading code from ksysguard */
float cpu_rt_usage(struct rt_process_info **rt_list_head)
{
// Watch out for SMP effects...
float result = 0.0f;
pid_t myself = getpid();
DIR* dir;
struct dirent* entry;
/* read in current process list via the /proc filesystem entry */
if ((dir = opendir("/proc")) == NULL)
{
perror("Cannot open directory \'/proc\'!\n"
"The kernel needs to be compiled with support\n"
"for /proc filesystem enabled!\n");
return 0;
}
// for all processes
while ((entry = readdir(dir)))
{
if (isdigit(entry->d_name[0]))
{
pid_t pid;
pid = atoi(entry->d_name);
if (pid != myself && isRT(pid)) {
struct rt_process_info *process = find_process(pid);
float cpu_use;
printf("Found a RT process %d, info 0x%x\n", pid, process);
if (process == NULL)
{
process = new_process(pid);
if (process == NULL) {
// to many RT processes!
// this process is new - assume a DOS attack
printf("Out of RT process info space - "
"assume DOS attack\n");
set_normal_priority(pid);
}
process->alive = 2; /* mark process new */
}
cpu_use = cpu_usage(process);
process->alive = 1;
result += cpu_use;
}
}
}
closedir(dir);
return result;
}
void gc_rt_processes()
{
unsigned ix;
for (ix = 0; ix < MAX_RT_PROCESSES; ix++)
{
struct rt_process_info *rt_examine = &rt_process[ix];
if (rt_examine->alive)
{
rt_examine->alive = 0;
}
else
{
rt_examine->pid = 0; /* delete it! */
}
}
}
int set_realtime_priority(void)
{
struct sched_param schp;
/*
* set the process to realtime privs
*/
memset(&schp, 0, sizeof(schp));
schp.sched_priority = sched_get_priority_max(SCHED_FIFO);
if (sched_setscheduler(0, SCHED_FIFO, &schp) != 0) {
perror("sched_setscheduler");
return -1;
}
if(mlockall(MCL_CURRENT|MCL_FUTURE))
{
perror("mlockall() failed, exiting. mlock");
return -1;
}
return 0;
}
int set_normal_priority(pid_t pid)
{
struct sched_param schp;
/*
* set the process to realtime privs
*/
memset(&schp, 0, sizeof(schp));
schp.sched_priority = 0;
printf("Attempt to reduce scheduling class for pid %d ", pid);
if (sched_setscheduler(pid, SCHED_OTHER, &schp) != 0) {
printf("- failed!\n");
perror("sched_setscheduler");
return -1;
}
printf("- done!\n");
return 0;
}
void set_normal_priority_all()
{
unsigned ix;
for (ix = 0; ix < MAX_RT_PROCESSES; ix++)
{
struct rt_process_info *process_info = &rt_process[ix];
if (process_info->pid)
set_normal_priority(process_info->pid);
}
}
int cpu_idle()
{
static int _user, _sys, _nice, _idle;
int user, sys, nice, idle;
int possible_rt_work, non_rt_work;
// read file /proc/stat (first line: user, sys, nice, idle)
FILE *stat = fopen("/proc/stat", "r");
fscanf(stat, "%*s %d %d %d %d", &user, &sys, &nice, &idle);
fclose(stat);
possible_rt_work = (user - _user + sys - _sys);
non_rt_work = (nice - _nice + idle - _idle);
_user = user; _sys = sys; _nice = nice; _idle = idle;
return 100 * non_rt_work / (non_rt_work + possible_rt_work);
}
int main(int argc, char * argv[])
{
struct rt_process_info *rt_list = NULL;
// monitor process runs with realtime prio
set_realtime_priority();
#define MIN_IDLE 10
#define MAX_RT_USAGE 70
while (1) {
if (cpu_idle() < MIN_IDLE) {
printf("Total CPU IDLE below MIN_IDLE\n");
if (cpu_rt_usage(&rt_list) > MAX_RT_USAGE) {
printf("Total CPU RT usage above MAX_RT_USAGE\n");
gc_rt_processes();
// build process trees from rt_list
// decide which tree to reduce to normal prio class
// (assume only one for simplicitly...)
// reduce all processes in that tree
set_normal_priority_all();
// (may use nice to simulate prio levels)
// log a message
}
}
sleep(10);
}
// process exiting - free elements on rt_list...
//free_rt_list(rt_list);
}