On Thursday, June 06, 2024 20:45 EEST, Kees Cook <k...@kernel.org> wrote:

> On Wed, Jun 05, 2024 at 07:49:31PM +0300, Adrian Ratiu wrote:
> > +   proc_mem.restrict_foll_force= [KNL]
> > +                   Format: {all | ptracer}
> > +                   Restricts the use of the FOLL_FORCE flag for 
> > /proc/*/mem access.
> > +                   If restricted, the FOLL_FORCE flag will not be added to 
> > vm accesses.
> > +                   Can be one of:
> > +                   - 'all' restricts all access unconditionally.
> > +                   - 'ptracer' allows access only for ptracer processes.
> > +                   If not specified, FOLL_FORCE is always used.
> 
> It dawns on me that we likely need an "off" setting for these in case it
> was CONFIG-enabled...
> 
> > +static int __init early_proc_mem_restrict_##name(char *buf)                
> >         \
> > +{                                                                          
> > \
> > +   if (!buf)                                                               
> > \
> > +           return -EINVAL;                                                 
> > \
> > +                                                                           
> > \
> > +   if (strcmp(buf, "all") == 0)                                            
> > \
> > +           static_key_slow_inc(&proc_mem_restrict_##name##_all.key);       
> > \
> > +   else if (strcmp(buf, "ptracer") == 0)                                   
> > \
> > +           static_key_slow_inc(&proc_mem_restrict_##name##_ptracer.key);   
> > \
> > +   return 0;                                                               
> > \
> > +}                                                                          
> > \
> > +early_param("proc_mem.restrict_" #name, early_proc_mem_restrict_##name)
> 
> Why slow_inc here instead of the normal static_key_enable/disable?
> 
> And we should report misparsing too, so perhaps:
> 
> static int __init early_proc_mem_restrict_##name(char *buf)                   
> \
> {                                                                             
> \
>       if (!buf)                                                               
> \
>               return -EINVAL;                                                 
> \
>                                                                               
> \
>       if (strcmp(buf, "all") == 0) {                                          
> \
>               static_key_enable(&proc_mem_restrict_##name##_all.key);         
> \
>               static_key_disable(&proc_mem_restrict_##name##_ptracer.key);    
> \
>       } else if (strcmp(buf, "ptracer") == 0) {                               
> \
>               static_key_disable(&proc_mem_restrict_##name##_all.key);        
> \
>               static_key_enable(&proc_mem_restrict_##name##_ptracer.key);     
> \
>       } else if (strcmp(buf, "off") == 0) {                                   
> \
>               static_key_disable(&proc_mem_restrict_##name##_all.key);        
> \
>               static_key_disable(&proc_mem_restrict_##name##_ptracer.key);    
> \
>       } else                                                                  
> \
>               pr_warn("%s: ignoring unknown option '%s'\n",                   
> \
>                       "proc_mem.restrict_" #name, buf);                       
> \
>       return 0;                                                               
> \
> }                                                                             
> \
> early_param("proc_mem.restrict_" #name, early_proc_mem_restrict_##name)
> 
> > +static int __mem_open_access_permitted(struct file *file, struct 
> > task_struct *task)
> > +{
> > +   bool is_ptracer;
> > +
> > +   rcu_read_lock();
> > +   is_ptracer = current == ptrace_parent(task);
> > +   rcu_read_unlock();
> > +
> > +   if (file->f_mode & FMODE_WRITE) {
> > +           /* Deny if writes are unconditionally disabled via param */
> > +           if 
> > (static_branch_maybe(CONFIG_PROC_MEM_RESTRICT_OPEN_WRITE_DEFAULT,
> > +                                   &proc_mem_restrict_open_write_all))
> > +                   return -EACCES;
> > +
> > +           /* Deny if writes are allowed only for ptracers via param */
> > +           if 
> > (static_branch_maybe(CONFIG_PROC_MEM_RESTRICT_OPEN_WRITE_PTRACE_DEFAULT,
> > +                                   &proc_mem_restrict_open_write_ptracer) 
> > &&
> > +               !is_ptracer)
> > +                   return -EACCES;
> > +   }
> > +
> > +   if (file->f_mode & FMODE_READ) {
> > +           /* Deny if reads are unconditionally disabled via param */
> > +           if 
> > (static_branch_maybe(CONFIG_PROC_MEM_RESTRICT_OPEN_READ_DEFAULT,
> > +                                   &proc_mem_restrict_open_read_all))
> > +                   return -EACCES;
> > +
> > +           /* Deny if reads are allowed only for ptracers via param */
> > +           if 
> > (static_branch_maybe(CONFIG_PROC_MEM_RESTRICT_OPEN_READ_PTRACE_DEFAULT,
> > +                                   &proc_mem_restrict_open_read_ptracer) &&
> > +               !is_ptracer)
> > +                   return -EACCES;
> > +   }
> > +
> > +   return 0; /* R/W are not restricted */
> > +}
> 
> Given how deeply some of these behaviors may be in userspace, it might
> be more friendly to report the new restrictions with a pr_notice() so
> problems can be more easily tracked down. For example:
> 
> static void report_mem_rw_rejection(const char *action, struct task_struct 
> *task)
> {
>       pr_warn_ratelimited("Denied %s of /proc/%d/mem (%s) by pid %d (%s)\n",
>                           action, task_pid_nr(task), task->comm,
>                           task_pid_nr(current), current->comm);
> }
> 
> ...
> 
>       if (file->f_mode & FMODE_WRITE) {
>               /* Deny if writes are unconditionally disabled via param */
>               if 
> (static_branch_maybe(CONFIG_PROC_MEM_RESTRICT_OPEN_WRITE_DEFAULT,
>                                       &proc_mem_restrict_open_write_all)) {
>                       report_mem_rw_reject("all open-for-write");
>                       return -EACCES;
>               }
> 
>               /* Deny if writes are allowed only for ptracers via param */
>               if 
> (static_branch_maybe(CONFIG_PROC_MEM_RESTRICT_OPEN_WRITE_PTRACE_DEFAULT,
>                                       &proc_mem_restrict_open_write_ptracer) 
> &&
>                   !is_ptracer)
>                       report_mem_rw_reject("non-ptracer open-for-write");
>                       return -EACCES;
>       }
> 
> etc
> 
> > +static bool __mem_rw_current_is_ptracer(struct file *file)
> > +{
> > +   struct inode *inode = file_inode(file);
> > +   struct task_struct *task = get_proc_task(inode);
> > +   struct mm_struct *mm = NULL;
> > +   int is_ptracer = false, has_mm_access = false;
> > +
> > +   if (task) {
> > +           rcu_read_lock();
> > +           is_ptracer = current == ptrace_parent(task);
> > +           rcu_read_unlock();
> > +
> > +           mm = mm_access(task, PTRACE_MODE_READ_FSCREDS);
> > +           if (mm && file->private_data == mm) {
> > +                   has_mm_access = true;
> > +                   mmput(mm);
> > +           }
> > +
> > +           put_task_struct(task);
> > +   }
> > +
> > +   return is_ptracer && has_mm_access;
> > +}
> 
> Thanks; this looks right to me now!
> 
> > +menu "Procfs mem restriction options"
> > +
> > +config PROC_MEM_RESTRICT_FOLL_FORCE_DEFAULT
> > +   bool "Restrict all FOLL_FORCE flag usage"
> > +   default n
> > +   help
> > +     Restrict all FOLL_FORCE usage during /proc/*/mem RW.
> > +     Debuggers like GDB require using FOLL_FORCE for basic
> > +     functionality.
> > +
> > +config PROC_MEM_RESTRICT_FOLL_FORCE_PTRACE_DEFAULT
> > +   bool "Restrict FOLL_FORCE usage except for ptracers"
> > +   default n
> > +   help
> > +     Restrict FOLL_FORCE usage during /proc/*/mem RW, except
> > +     for ptracer processes. Debuggers like GDB require using
> > +     FOLL_FORCE for basic functionality.
> 
> Can we adjust the Kconfigs to match the bootparam arguments? i.e.
> instead of two for each mode, how about one with 3 settings ("all",
> "ptrace", or "off")
> 
> choice
>       prompt "Restrict /proc/pid/mem FOLL_FORCE usage"
>       default PROC_MEM_RESTRICT_FOLL_FORCE_OFF
>       help
>         Reading and writing of /proc/pid/mem bypasses memory permission
>         checks due to the internal use of the FOLL_FORCE flag. This can be
>         used by attackers to manipulate process memory contents that
>         would have been otherwise protected. However, debuggers, like GDB,
>         use this to set breakpoints, etc. To force debuggers to fall back
>         to PEEK/POKE, see PROC_MEM_RESTRICT_OPEN_WRITE_ALL.
> 
>       config PROC_MEM_RESTRICT_FOLL_FORCE_OFF
>       bool "Do not restrict FOLL_FORCE usage with /proc/pid/mem (regular)"
>       help
>         Regular behavior: continue to use the FOLL_FORCE flag for
>         /proc/pid/mem access.
> 
>       config PROC_MEM_RESTRICT_FOLL_FORCE_PTRACE
>       bool "Only allow ptracers to use FOLL_FORCE with /proc/pid/mem (safer)"
>       help
>         Only use the FOLL_FORCE flag for /proc/pid/mem access when the
>         current task is the active ptracer of the target task. (Safer,
>         least disruptive to most usage patterns.)
> 
>       config PROC_MEM_RESTRICT_FOLL_FORCE_ALL
>       bool "Do not use FOLL_FORCE with /proc/pid/mem (safest)"
>       help
>         Remove the FOLL_FORCE flag for all /proc/pid/mem accesses.
>         (Safest, but may be disruptive to some usage patterns.)
> endchoice
> 
> Then the static_keys can be defined like this mess (I couldn't find a
> cleaner way to do it):
> 
> #define DEFINE_STATIC_KEY_PROC_MEM_ALL(name) \
>       DEFINE_STATIC_KEY_TRUE_RO(proc_mem_restrict_##name##_all);      \
>       DEFINE_STATIC_KEY_FALSE_RO(proc_mem_restrict_##name##_ptracer);
> #define DEFINE_STATIC_KEY_PROC_MEM_PTRACE(name) \
>       DEFINE_STATIC_KEY_FALSE_RO(proc_mem_restrict_##name##_all);     \
>       DEFINE_STATIC_KEY_TRUE_RO(proc_mem_restrict_##name##_ptracer);
> #define DEFINE_STATIC_KEY_PROC_MEM_OFF(name) \
>       DEFINE_STATIC_KEY_FALSE_RO(proc_mem_restrict_##name##_all);     \
>       DEFINE_STATIC_KEY_FALSE_RO(proc_mem_restrict_##name##_ptracer);
> 
> #define DEFINE_STATIC_KEY_PROC_MEM_0(level, name)
> #define DEFINE_STATIC_KEY_PROC_MEM_1(level, name)             \
>       DEFINE_STATIC_KEY_PROC_MEM_##level(name)
> 
> #define _DEFINE_STATIC_KEY_PROC_MEM_PICK(enabled, level, name)   \
> DEFINE_STATIC_KEY_PROC_MEM_##enabled(level, name)
> 
> #define DEFINE_STATIC_KEY_PROC_MEM_PICK(enabled, level, name)   \
> _DEFINE_STATIC_KEY_PROC_MEM_PICK(enabled, level, name)
> 
> #define DEFINE_STATIC_KEY_PROC_MEM(CFG, name)                 \
> DEFINE_STATIC_KEY_PROC_MEM_PICK(IS_ENABLED(CONFIG_PROC_MEM_RESTRICT_##CFG##_ALL),
>  ALL, name)
> DEFINE_STATIC_KEY_PROC_MEM_PICK(IS_ENABLED(CONFIG_PROC_MEM_RESTRICT_##CFG##_PTRACE),
>  PTRACE, name)
> DEFINE_STATIC_KEY_PROC_MEM_PICK(IS_ENABLED(CONFIG_PROC_MEM_RESTRICT_##CFG##_OFF),
>  OFF, name)
> 
> #define DEFINE_EARLY_PROC_MEM_RESTRICT(CFG, name)                             
> \
> DEFINE_STATIC_KEY_PROC_MEM(CFG, name)                                         
> \
> static int __init early_proc_mem_restrict_##name(char *buf)                   
> \
> {                                                                             
> \
>       if (!buf)                                                               
> \
>               return -EINVAL;                                                 
> \
>                                                                               
> \
>       if (strcmp(buf, "all") == 0) {                                          
> \
>               static_key_enable(&proc_mem_restrict_##name##_all.key);         
> \
>               static_key_disable(&proc_mem_restrict_##name##_ptracer.key);    
> \
>       } else if (strcmp(buf, "ptracer") == 0) {                               
> \
>               static_key_disable(&proc_mem_restrict_##name##_all.key);        
> \
>               static_key_enable(&proc_mem_restrict_##name##_ptracer.key);     
> \
>       } else if (strcmp(buf, "off") == 0) {                                   
> \
>               static_key_disable(&proc_mem_restrict_##name##_all.key);        
> \
>               static_key_disable(&proc_mem_restrict_##name##_ptracer.key);    
> \
>       } else                                                                  
> \
>               pr_warn("%s: ignoring unknown option '%s'\n",                   
> \
>                       "proc_mem.restrict_" #name, buf);                       
> \
>       return 0;                                                               
> \
> }                                                                             
> \
> early_param("proc_mem.restrict_" #name, early_proc_mem_restrict_##name)
> 
> DEFINE_EARLY_PROC_MEM_RESTRICT(OPEN_READ, open_read);
> DEFINE_EARLY_PROC_MEM_RESTRICT(OPEN_WRITE, open_write);
> DEFINE_EARLY_PROC_MEM_RESTRICT(WRITE, write);
> DEFINE_EARLY_PROC_MEM_RESTRICT(FOLL_FORCE, foll_force);

Hello again,

I tried very hard to make the above work these past few days and gave up.
Couldn't find a way to get it to compile.
Tried to also debug the compiler preprocess output and my head hurts. :)

Would macros like the following be acceptable?
I know it's more verbose but also much easier to understand and it works.

#if IS_ENABLED(CONFIG_PROC_MEM_RESTRICT_OPEN_READ_ALL)
DEFINE_STATIC_KEY_TRUE_RO(proc_mem_restrict_open_read_all);
DEFINE_STATIC_KEY_FALSE_RO(proc_mem_restrict_open_read_ptracer);
#elif IS_ENABLED(CONFIG_PROC_MEM_RESTRICT_OPEN_READ_PTRACE)
DEFINE_STATIC_KEY_FALSE_RO(proc_mem_restrict_open_read_all);
DEFINE_STATIC_KEY_TRUE_RO(proc_mem_restrict_open_read_ptracer);
#else
DEFINE_STATIC_KEY_FALSE_RO(proc_mem_restrict_open_read_all);
DEFINE_STATIC_KEY_FALSE_RO(proc_mem_restrict_open_read_ptracer);
#endif


Reply via email to