c - 如何在Linux 3.5.4中从自定义系统调用中调用系统调用

标签 c linux filesystems kernel system-calls

我正在Linux中实现自己的系统调用。它正在其中调用重命名系统调用。它使用用户参数(下面是代码)将代码传递给重命名。

这是基本代码:

int sys_mycall(const char __user * inputFile)   {

//
// Code to generate my the "fileName"
//
//

old_fs = get_fs();
set_fs(KERNEL_DS);

    ans =  sys_renameat(AT_FDCWD, fileName, AT_FDCWD, inputFile);

set_fs(old_fs);

    return ans;

}

我在这里有两个疑问。
  • 我正在使用old_fs = get_fs();set_fs(KERNEL_DS);set_fs(old_fs);绕过对sys_rename的实际调用,因为发生了错误。我从以下问题得到了答案:allocate user-space memory from kernel ...这是正确的解决方法吗?
  • 如何从系统调用
  • 调用系统调用

    编辑:
    int sys_myfunc(const char __user * inputFileUser)   {
    
    
        char inputFile[255];
        int l = 0;
        while(inputFileUser[l] != '\0') l++;
    
        if(l==0)
            return -10; 
    
        if(copy_from_user(inputFile,inputFileUser,l+1)< 0 ) return -20;
    //
    //GENERATE fileName here
    //
    //
    
        char fileName[255];
        return  sys_renameat(AT_FDCWD, inputFile, AT_FDCWD, fileName);
    
    }
    

    以下内容仍返回-1。为什么?我将数据复制到内核空间。

    最佳答案

    我想确切地说明实现脚注所需的正确方法,但是最初的答案太长了,我决定将解决方案放在一个单独的答案中。我将代码分成几部分,并解释每个片段的作用。

    请记住,由于我们重复使用了内核代码,因此本文中的代码和所产生的功能必须根据GPLv2许可进行许可。

    首先,我们首先声明一个单参数syscall。

    SYSCALL_DEFINE1(myfunc, const char __user *, oldname)
    {
    

    在内核中,堆栈空间是一种稀缺资源。您不创建本地数组;而是创建本地数组。您始终使用动态内存管理。幸运的是,有一些非常有用的功能,例如__getname(),因此几乎没有其他代码。重要的是要记住在完成使用后释放所有使用的内存。

    由于此系统调用基本上是rename的变体,因此我们几乎重用了所有fs/namei.c:sys_renameat()代码。首先,局部变量声明。也有很多。就像我说的那样,堆栈在内核中很稀少,并且在任何syscall函数中您都不会看到比这更多的局部变量:
        struct dentry *old_dir, *new_dir;
        struct dentry *old_dentry, *new_dentry;
        struct dentry *trap;
        struct nameidata oldnd, newnd;
        char *from;
        char *to = __getname();
        int error;
    
    sys_renameat()的第一个更改已经在上面的char *to = __getname();行上。它动态分配PATH_MAX+1字节,并且在不再需要它之后必须使用__putname()释放它。这是为文件或目录名称声明临时缓冲区的正确方法。

    要构建新路径(to),我们还需要能够直接访问旧名称(from)。由于内核用户空间的障碍,我们不能直接访问oldname。因此,我们为其创建内核内副本:
        from = getname(oldname);
        if (IS_ERR(from)) {
            error = PTR_ERR(from);
            goto exit;
        }
    

    尽管许多C程序员都被告知goto是邪恶的,但这是异常(exception):错误处理。不必记住我们需要做的所有清理工作(并且我们已经至少需要做__putname(to)了),我们将清理工作放在函数的末尾,然后跳到正确的位置,exit是最后一个。 error当然包含错误号。

    在我们的函数的这一点上,我们可以访问from[0]直到第一个'\0',或者访问(并包括)from[PATH_MAX],以第一个为准。它是普通的内核端数据,并且以与任何C代码相同的普通方式进行访问。

    您还保留了新名称to[0]直到包括to[PATH_MAX]的内存。请记住要确保也使用\0(在to[PATH_MAX] = '\0'或更早的索引中)将其终止。

    在为to构建内容之后,我们需要进行路径查找。与renameat()不同,我们不能使用user_path_parent()。但是,我们可以看看user_path_parent()做什么,并做同样的工作-当然,要适应我们自己的需求。事实证明,它仅通过错误检查调用do_path_lookup()。因此,可以将两个user_path_parent()调用及其错误检查替换为
        error = do_path_lookup(AT_FDCWD, from, LOOKUP_PARENT, &oldnd);
        if (error)
            goto exit0;
    
        error = do_path_lookup(AT_FDCWD, to, LOOKUP_PARENT, &newnd);
        if (error)
            goto exit1;
    

    请注意,exit0是在原始renameat()中找不到的新标签。我们需要一个新标签,因为在exit处,我们只有to;但是在exit0处,我们同时拥有tofrom。在exit0之后,我们有tofromoldnd,依此类推。

    接下来,我们可以重用大部分sys_renameat()。它为重命名做了所有艰苦的工作。为了节省空间,我将完全忽略它的作用,因为您可以相信,如果rename()有效,那么它也会起作用。
        error = -EXDEV;
        if (oldnd.path.mnt != newnd.path.mnt)
            goto exit2;
    
        old_dir = oldnd.path.dentry;
        error = -EBUSY;
        if (oldnd.last_type != LAST_NORM)
            goto exit2;
    
        new_dir = newnd.path.dentry;
        if (newnd.last_type != LAST_NORM)
            goto exit2;
    
        error = mnt_want_write(oldnd.path.mnt);
        if (error)
            goto exit2;
    
        oldnd.flags &= ~LOOKUP_PARENT;
        newnd.flags &= ~LOOKUP_PARENT;
        newnd.flags |= LOOKUP_RENAME_TARGET;
    
        trap = lock_rename(new_dir, old_dir);
    
        old_dentry = lookup_hash(&oldnd);
        error = PTR_ERR(old_dentry);
        if (IS_ERR(old_dentry))
            goto exit3;
        /* source must exist */
        error = -ENOENT;
        if (!old_dentry->d_inode)
            goto exit4;
        /* unless the source is a directory trailing slashes give -ENOTDIR */
        if (!S_ISDIR(old_dentry->d_inode->i_mode)) {
            error = -ENOTDIR;
            if (oldnd.last.name[oldnd.last.len])
                goto exit4;
            if (newnd.last.name[newnd.last.len])
                goto exit4;
        }
        /* source should not be ancestor of target */
        error = -EINVAL;
        if (old_dentry == trap)
            goto exit4;
        new_dentry = lookup_hash(&newnd);
        error = PTR_ERR(new_dentry);
        if (IS_ERR(new_dentry))
            goto exit4;
        /* target should not be an ancestor of source */
        error = -ENOTEMPTY;
        if (new_dentry == trap)
            goto exit5;
    
        error = security_path_rename(&oldnd.path, old_dentry,
                         &newnd.path, new_dentry);
        if (error)
            goto exit5;
    
        error = vfs_rename(old_dir->d_inode, old_dentry,
                       new_dir->d_inode, new_dentry);
    

    至此,所有工作都已经完成,只剩下释放上面代码所占用的锁,内存等。如果此时一切都成功,则error == 0,我们将进行所有清理。如果遇到问题,error将包含错误代码,并且我们已跳至正确的标签以对发生错误的位置进行必要的清除。如果vfs_rename()失败-它会执行实际操作-我们将清除所有内容。

    但是,与原始代码相比,我们首先拥有from(exit),紧随其后的是to(exit0),然后是dentry查找。因此,我们需要将它们释放到正确的位置(在最后时,因为它们首先完成。清除过程当然是相反的):
    exit5:
        dput(new_dentry);
    exit4:
        dput(old_dentry);
    exit3:
        unlock_rename(new_dir, old_dir);
        mnt_drop_write(oldnd.path.mnt);
    exit2:
        path_put(&newnd.path);
    exit1:
        path_put(&oldnd.path);
    exit0:
        putname(from);
    exit:
        __putname(to);
        return error;
    }
    

    至此我们完成了。

    当然,在上面我们从sys_renameat()复制的部分中,有很多细节需要考虑-就像我在另一个答案中所说的那样,您不仅应该复制这样的代码,还应该将通用代码重构为一个辅助函数。这使维护更加容易。幸运的是,由于我们保留了renameat()中的所有检查-我们在复制任何renameat()代码之前进行了路径操作-我们可以确保完成所有必要的检查。就像用户自己指定操纵路径并将其称为renameat()一样。

    如果要在已经进行一些检查之后进行修改,则情况将更加复杂。您将不得不考虑这些检查是什么,您的修改如何影响它们,并且几乎总是要重新执行那些检查。

    提醒任何读者,您不能仅在自己的syscall中创建文件名或任何其他字符串然后再调用另一个syscall的原因是,您刚创建的字符串位于内核用户空间边界的内核侧,而syscall期望数据驻留在另一用户空间侧。在x86上,您可能会意外地从内核侧刺穿边界,但这并不意味着您应该这样做:有copy_from_user()copy_to_user()及其派生词(例如strncpy_from_user()),必须使用用于此目的。不必费劲地调用另一个系统调用,而是关于所提供数据的位置(内核或用户空间)的问题。

    关于c - 如何在Linux 3.5.4中从自定义系统调用中调用系统调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12878228/

    相关文章:

    c - memcpy 的奇怪结果

    linux - 如何在 hadoop 中恢复 Task Tracker 和 Job Tracker?

    PHP mkdir( $recursive = true ) 跳过最后一个目录

    linux - Linux 机器上的最大子目录数

    objective-c - AVCaptureDevice 的 USB 接口(interface)

    c - 在 Arduino 中使用 PROGMEM 存储在闪存中的字符串数组

    c - 为什么 scanf 在格式不匹配时返回随机值

    linux - 打开http ://localhost in linux cmd

    linux - 将多个 gif 转换为单个 eps

    windows - 有没有办法在文件系统上伪造文件或编写一个只对我的 EXE 文件可见的文件