0

PTRACE_PEEKUSER is documented to read a word from the user area, whose partial layout on x86_64 is:

struct user
{
  struct user_regs_struct       regs;
  int                           u_fpvalid;
  struct user_fpregs_struct     i387;
  //...
  int                           u_debugreg [8];
};

While one can happily call PTRACE_PEEKUSER with an offset which lies within regs or u_debugreg, passing an offset which is within i387 returns EIO unconditionally.

Looking at the code in the kernel, I can see that reading inside regs and u_debugreg are the only supported offsets:

    case PTRACE_PEEKUSR: {
        unsigned long tmp;

        ret = -EIO;
        if ((addr & (sizeof(data) - 1)) || addr >= sizeof(struct user))
            break;

        tmp = 0;  /* Default return condition */
        if (addr < sizeof(struct user_regs_struct))
            tmp = getreg(child, addr);
        else if (addr >= offsetof(struct user, u_debugreg[0]) &&
             addr <= offsetof(struct user, u_debugreg[7])) {
            addr -= offsetof(struct user, u_debugreg[0]);
            tmp = ptrace_get_debugreg(child, addr / sizeof(data));
        }
        ret = put_user(tmp, datap);
        break;
    }

I know that reading inside i387 is not supported. I know that I can read those registers by calling PTRACE_GETFPREGS instead. My question is: is there a specific reason why reading those registers using PTRACE_PEEKUSER and writing them with PTRACE_POKEUSER is disabled?

My guess is that it's because most of the fields in user_fpregs_struct are smaller than 64 bits, therefore reads and writes may cover multiple registers. However, I'm writing a resource which will be public and will have a note on this, so I'd rather be certain that my guess is right.

TartanLlama
  • 101
  • 3

1 Answers1

1

Just a guess: You want to read the Math Co-processor registers. Have you checked when the i387 member of struct user in the kernel gets updated? I haven't, and I could be wrong, but my guess is not often, but in cases like core dumps, for example.

From arch/x86/include/asm/user_64.h

struct user {
/* We start with the registers, to mimic the way that "memory" is returned
   from the ptrace(3,...) function.  */
  struct user_regs_struct regs; /* Where the registers are actually stored */
/* ptrace does not yet supply these.  Someday.... */
  int u_fpvalid;        /* True if math co-processor being used. */
                /* for this mess. Not yet used. */
  int pad0;
  struct user_i387_struct i387; /* Math Co-processor registers. */
  :
  unsigned long u_debugreg[8];
  :
};

So, reading Math Co-processor registers is best done by reading from the FPU. If you check how kernel does the PTRACE_GETFPREGS, you'll see it's getting it from FPU, and not the struct user i387 member. Same goes for writing Math Co-processor registers using PTRACE_SETFPREGS.

From arch_ptrace

case PTRACE_GETFPREGS:  /* Get the child FPU state. */
    return copy_regset_to_user(child,
                   task_user_regset_view(current),
                   REGSET_FP,
                   0, sizeof(struct user_i387_struct),
                   datap);

task_user_regset_view return x86_64_regsets that uses xfpregs_get for reading.

static struct user_regset x86_64_regsets[] __ro_after_init = {

    [REGSET_FP] = {
        .core_note_type = NT_PRFPREG,
        .n = sizeof(struct user_i387_struct) / sizeof(long),
        .size = sizeof(long), .align = sizeof(long),
        .active = regset_xregset_fpregs_active, .regset_get = xfpregs_get, .set = xfpregs_set
    },

}

const struct user_regset_view *task_user_regset_view(struct task_struct *task)
{
    :
#ifdef CONFIG_X86_64
    return &user_x86_64_view;
#endif
}
dhanushka
  • 276
  • 1
  • 4