функция чтения в C возвращает 0, даже если это не EOF

#c

Вопрос:

Я читаю книгу «Хакерство: искусство эксплуатации». Есть небольшая программа, которая читает /var/notes, которая является двоичным файлом, сгенерированным мной, для поиска и вывода некоторых строк. Тем не менее, функция чтения в функции print_notes всегда возвращает 0, и буфера note_buffer вообще не затрагивается. Итак, каждый раз, когда я получаю какой-то нечитаемый вывод с консоли, такой как этот:

 $ ./notesearch  [DEBUG] found a 10 byte note for user id 1000 �w}�-------[ end of note data ]-------  

Содержание /var/примечаний:

 $ sudo hexdump -C /var/notes 00000000 e8 03 00 00 0a 61 61 61 62 62 62 63 63 63 0a |.....aaabbbccc.| 0000000f   

Первые 4 байта обозначают целое число (в данном случае 1000), а остальные-это просто код ASCII.

Эта программа предназначена для изучения setuid, поэтому разрешения на файлы устанавливаются, как показано ниже:

 $ ll ./notesearch /var/notes  -rwsrwxr-x 1 root root 22K Nov 19 22:23 ./notesearch -rw------- 1 root root 15 Nov 19 22:22 /var/notes  

Кто-нибудь знает, почему функция чтения всегда возвращает 0? Кстати, я использую Ubuntu 20.04 и GCC 9.3.

Вот код:

 #include lt;stdio.hgt; #include lt;string.hgt; #include lt;fcntl.hgt; #include lt;sys/stat.hgt; #include "hacking.h"  #define FILENAME "/var/notes"  int print_notes(int, int, char *); // note printing function int find_user_note(int, int); // seek in file for a note for user int search_note(char *, char *); // search for keyword function void fatal(char *); // fatal error handler  int main(int argc, char *argv[]) {  int userid, printing=1, fd; // file descriptor  char searchstring[100];   if(argc gt; 1) // If there is an arg  strcpy(searchstring, argv[1]); // that is the search string  else // otherwise  searchstring[0] = 0; // search string is empty   userid = getuid();  fd = open(FILENAME, O_RDONLY); // open the file for read-only access  if(fd == -1)  fatal("in main() while opening file for reading");   while(printing)  printing = print_notes(fd, userid, searchstring);  printf("-------[ end of note data ]-------n");  close(fd); }  // A function to print the notes for a given uid that match // an optional search string // returns 0 at end of file, 1 if there are still more notes int print_notes(int fd, int uid, char *searchstring) {  int note_length;  char byte=0, note_buffer[100];    note_length = find_user_note(fd, uid);  if(note_length == -1) // if end of file reached  return 0; // return 0   read(fd, note_buffer, note_length); // read note data  note_buffer[note_length] = 0; // terminate the string    if(search_note(note_buffer, searchstring)) // if searchstring found  printf(note_buffer); // print the note  return 1; }  // A function to find the next note for a given userID // returns -1 if the end of the file is reached // otherwise it returns the length of the found note int find_user_note(int fd, int user_uid) {  int note_uid=-1;  unsigned char byte;  int length;   while(note_uid != user_uid) { // loop until a note for user_uid is found  if(read(fd, amp;note_uid, 4) != 4) // read the uid data  return -1; // if 4 bytes aren't read, return end of file code  if(read(fd, amp;byte, 1) != 1) // read the newline separator  return -1;   byte = length = 0;  while(byte != 'n') { // figure out how many bytes to the end of line  if(read(fd, amp;byte, 1) != 1) // read a single byte  return -1; // if byte isn't read, return end of file code  length  ;   }  }  lseek(fd, length * -1, SEEK_CUR); // rewind file reading by length bytes   printf("[DEBUG] found a %d byte note for user id %dn", length, note_uid);  return length; }  // A function to search a note for a given keyword // returns 1 if a match is found, 0 if there is no match int search_note(char *note, char *keyword) {  int i, keyword_length, match=0;   keyword_length = strlen(keyword);  if(keyword_length == 0) // if there is no search string  return 1; // always "match"    for(i=0; i lt; strlen(note); i  ) { // iterate over bytes in note  if(note[i] == keyword[match]) // if byte matches keyword  match  ; // get ready to check the next byte  else { // otherwise  if(note[i] == keyword[0]) // if that byte matches first keyword byte  match = 1; // start the match count at 1  else  match = 0; // otherwise it is zero  }  if(match == keyword_length) // if there is a full match  return 1; // return matched  }  return 0; // return not matched }  

Комментарии:

1. Ваш двоичный файл очень любопытен. Зачем вам двоичное целое число в файле с символами новой строки? Вы просто используете их для разделителей? Ваш отладочный вывод отличается от одного, так как у вас есть 9 байтов в примечании между разделителями (например "aaabbbccc" ), что "[DEBUG] found a 10 byte note ..." выглядит немного странно. Похоже, вы хотите while (read(fd, amp;byte, 1) == 1) { ... } , а не заполняете byte и сравниваете с 'n' условием. Вероятно, есть и другие проблемы, но это выделялось.

Ответ №1:

После долгих поисков я наконец нахожу причину. Итак, мне не хватает #include lt;unistd.hgt;, если я включу файл заголовка, все будет работать нормально.

Я продолжил поиск причин, по которым этот код может компилироваться и запускаться без включения заголовка. Оказывается, что если GCC видит функцию без объявления, он делает некоторые предположения относительно функции (одно из них возвращает значение int). Однако, если истинное определение функции отличается от предположения, программа будет иметь неопределенное поведение. В этом случае истинным объявлением для lseek является

 extern __off_t lseek (int __fd, __off_t __offset, int __whence) __THROW;  

но компилятор предполагает, что объявление будет

 int lseek(...)  

Таким образом, программа ведет себя неопределенно, поскольку она возвращает содержимое из регистра, в котором обычно хранится возвращаемое значение int.

Кстати, программа может передать компоновщик, потому что в стандартной библиотеке есть функция под названием lseek, которая соответствует вызову в print_notes.

Комментарии:

1. Если это так, разве вы не получили предупреждение «неявное объявление»? Это для того, чтобы предупредить вас именно об этом случае; это очень серьезное предупреждение, которое не следует игнорировать. (Хотя, если на то пошло, также не следует предупреждать компилятор, пока вы не докажете, что он безвреден).

2. @NateEldridge Я действительно получил несколько предупреждений, но я просто проигнорировал их. Я ожидал, что код не сможет скомпилироваться, если произойдет такое пропущенное объявление, так как я больше привык к c .

3. Что ж, тогда, надеюсь, урок усвоен. Пропущенные декларации были незаконны в C начиная с C99, но стандарт требует только «диагностики», которая не обязательно должна быть фатальной. Возможно, к сожалению, многие компиляторы, включая GCC, просто выдадут предупреждение и вернутся к неявному поведению int в C89. Поэтому вам нужно следить за предупреждениями — и, конечно, есть множество других предупреждений, которые компилятор может выдавать для кода, который является совершенно законным C, но не тем, что вам нужно. Многие люди советуют использовать -Wall -Werror так, чтобы предупреждения становились фатальными и вы вообще не могли их игнорировать.