文件成功打开但读取时出现“坏文件描述符”错误,stat显示文件大小为零。

6
我正在处理一个任务,需要对使用较差加密算法加密的PDF进行密码破解分析。
教授提供的代码使用fd=open(filename, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR)创建了加密文件。
在我的解密代码中,我使用fd_in=open(file, O_RDONLY)打开该文件。
问题是,当我尝试从文件中读取内容时,出现了“坏的文件描述符”错误。我使用stat尝试获取更多关于已打开文件的文件描述符的信息,它显示该文件长度为0,实际上它有几百KB。
我正在使用以下调试代码:
if (0 > (len = read(fd_in, (char*)&read_buff, BITE))) {    // BITE is defined as 8
  printf("Error occured grabbing first bite of %s.\n", file);
  printf("%s.\n", strerror(errno));

  struct stat fileStat;
  int stat = fstat(fd_in, &fileStat);

  printf("fstat returned: %d.\n", stat);      // Consistently printing 0
  printf("Information for %s\n",file);
  printf("---------------------------\n");
  printf("File Size: \t\t%d bytes\n",fileStat.st_size);
  printf("Number of Links: \t%d\n",fileStat.st_nlink);
  printf("File inode: \t\t%d\n",fileStat.st_ino);

  printf("File Permissions: \t");
  printf( (S_ISDIR(fileStat.st_mode)) ? "d" : "-");
  printf( (fileStat.st_mode & S_IRUSR) ? "r" : "-");
  printf( (fileStat.st_mode & S_IWUSR) ? "w" : "-");
  printf( (fileStat.st_mode & S_IXUSR) ? "x" : "-");
  printf( (fileStat.st_mode & S_IRGRP) ? "r" : "-");
  printf( (fileStat.st_mode & S_IWGRP) ? "w" : "-");
  printf( (fileStat.st_mode & S_IXGRP) ? "x" : "-");
  printf( (fileStat.st_mode & S_IROTH) ? "r" : "-");
  printf( (fileStat.st_mode & S_IWOTH) ? "w" : "-");
  printf( (fileStat.st_mode & S_IXOTH) ? "x" : "-");
  printf("\n\n");

  return 1;

我得到的结果是:

Error occured grabbing first bite of enc.pdf.
Bad file descriptor.
Information for enc.pdf
---------------------------
File Size:      0 bytes
Number of Links:    1
File inode:         16441996
File Permissions:   -rw-------

ls命令报告文件为

-rw-------  1 matt  matt   157887 Oct 29 03:01 enc.pdf

与打开文件相关的代码:
int fd_in=open(file, O_RDONLY);
if(fd_in<0) {
   printf("Failed to open the input file %s.\n", file);
   return 1;
} else {
    printf("File open, descriptor is: %d.\n", fd_in);
}

这一行代码一直打印出文件描述符的值为3。

关于read_buff有一些疑问。加密/解密过程涉及对读取的值进行异或运算。因此,缓冲区被声明为unsigned long long,为了读入它,我需要取地址并将其强制转换为(char*)类型。这种策略直接来自教授用于创建加密文件的代码。

我甚至添加了一个else语句和printf语句来验证文件描述符是否有效。目前,它似乎始终是3,而不是-1


@hmjd:刚测试了一下,似乎不相关。我还尝试在读取之前将errno设置为几个随机值,以验证错误消息是否发生变化。相当有信心它是由read(..)设置的。 - Matt
1 应用程序是否并行执行:它是否是多线程的? 2 read_buff 是如何声明的? - alk
1
你有打开文件的权限吗?你检查过能否打开该文件吗?open()返回了-1吗?使用fopen()怎么样? - Hong Zhou
1
你对检查read()的结果非常谨慎。抱歉问一下,但是代码不在这里,所以...你在检查open()的返回结果时是否同样谨慎?即使在open()之后,fd_in是否有效(因为根据错误消息,它并不有效)。 - WhozCraig
3
好的,我得出结论是在open()read(11,...)之间的代码以某种方式破坏了栈,并改变了fd_in的值,将其从3更改为11 - alk
显示剩余14条评论
3个回答

4

您可能想要检查在调用open()read()之间堆栈是否被破坏,以便文件描述符fd_in的值会发生改变。


2
任何偶然看到这个答案的人都应该查看问题的评论以获取更多细节。 - Matt

0

您忽略了对各种运算符结果的检查。我几乎可以肯定在上面列出的代码中fd_in == -1

要使用低级I/O打开数据文件,您需要执行以下操作:

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>

...

    int  fd;

    do {
        fd = open(filename, O_RDONLY | O_NOCTTY);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1) {
        const char *const errmsg = strerror(errno);
        fprintf(stderr, "%s: %s.\n", filename, errmsg);
        exit(1);
    }

这是因为open()总是可以被中断。

read()也可以被中断,但通常它只会返回一个短计数。

不,你不能只是说“但通常它会读取我要求的所有内容,如果发生了我以后再修复”,因为它实际上取决于系统细节——特别是文件所在的文件系统。在不同的系统上运行将产生不同的结果——更不用说如果它不是一个文件,而是一个命名管道或字符设备。只有白痴才会假设read()总是(甚至通常)读取你要求的所有内容。

强大的方法是使用一个辅助函数,可能是以下内容之一:

#include <unistd.h>
#include <sys/types.h>
#include <sched.h>
#include <errno.h>

/* Read between minimum and maximum bytes (inclusive)
 * from descriptor to buffer. Save the number of bytes
 * read into *bytes if bytes is not NULL.
 * The function returns 0 if successful, errno error otherwise.
 * If there is less input than minimum bytes, the function
 * will return ENOENT.
*/
static inline int readfd(const int descriptor,
                         void *const buffer,
                         const size_t minimum,
                         const size_t maximum,
                         size_t *const bytes)
{
    size_t   have = 0;
    ssize_t  n;

    while (have < minimum) {

        n = read(descriptor, (char *)buffer + have, maximum - have);
        if (n > (ssize_t)0) {
            have += n;
            if (bytes)
                *bytes = have;

        } else
        if (n == (ssize_t)0) {
            /* EOF, and have < minimum. */
            return errno = ENOENT;

        } else
        if (n != (ssize_t)-1) {
            /* A rare I/O error (Linux kernel bug). */
            return errno = EIO;

        } else
        if (errno == EWOULDBLOCK || errno == EAGAIN) {
            /* Nonblocking descriptor; ouch. Busy-loop. */
            sched_yield();

        } else
        if (errno != EINTR)
            return errno;
    }

    return 0;
}

该函数将在指定的文件描述符上读取介于minimummaximum字节之间(包括这两个值),并将其存储到指定的buffer中。如果成功,它将返回0,否则将返回一个非零的errno。如果没有足够的数据来满足最小值,则会返回ENOENT
即使在非阻塞描述符上,它也可以正常工作,通过在非阻塞描述符上忙等待(每次重试失败时仅放弃当前时间片)。这意味着不建议在非阻塞描述符上使用它,但如果由于某种奇怪的原因需要使用,则可以使用。你只是不应该需要使用它。(事实上,我个人会删除那一部分——EAGAIN/EWOULDBLOCK检查和sched_yield()——所以如果描述符是或必须是非阻塞的,我会注意到并修复我的代码以使用正确的方法。)

0

抱歉,这应该是一条注释。无法从此处进行评论。 read_buff是什么?为什么要将其转换为char*类型?

如果 read_buff被定义为char *read_buff;,那么(fd, &read_buff, LEN);可能会执行缓冲区溢出,覆盖您的自动变量。


read_buff 是一个 unsigned long long,因此我可以在解密的过程中将其异或,因此需要取地址并转换为 char* - Matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接