Đề tài 3. Lỗi định dạng chuỗi

You might also like

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 13

HỌC VIỆN KỸ THUẬT MẬT MÃ

Khoa An toàn thông tin

KỸ THUẬT LẬP TRÌNH AN TOÀN

LỖI ĐỊNH DẠNG CHUỖI

Giảng viên hướng dẫn:


Bùi Việt Thắng

Sinh viên thực hiện:


Vũ Viết Tùng
Vũ Trần Hoàng Anh
Kim Việt Anh
Nguyễn Quang Huy
CÁC HÀM NHẬP - XUẤT 3
Scanf() và các hàm liên quan. 3
Cú pháp 3
Tính năng không an toàn. 3
Tràn bộ đệm. 3
Đối số %c và %s dùng cho dữ liệu kiểu char. 4
Giải pháp đề xuất. 5
Xử lý tràn bộ đệm. 5
Xử lý ghi xâu. 5
Gets() và các hàm liên quan. 5
Cú pháp. 5
Tính năng không an toàn. 6
Giải pháp đề xuất. 6
Printf() và các hàm liên quan. 7
Cú pháp. 7
Tính năng không an toàn. 7
Giải pháp đề xuất. 8
CÁC THAO TÁC TRÊN XÂU 9
Hàm sao chép xâu. 9
Cú pháp. 9
Tính năng không an toàn. 9
Giải pháp đề xuất. 10
Hàm nối xâu. 11
Cú pháp. 11
Tính năng không an toàn. 12
Giải pháp đề xuất. 12

2
CÁC HÀM NHẬP - XUẤT

1. Scanf() và các hàm liên quan.


1.1. Cú pháp
Trong C, hàm scanf(); được dùng để lấy dữ liệu vào từ bàn phím và được quy định trong
thư viện stdio.h của C. scanf() được quy định cú pháp như sau:
scanf("format string", argument_list);
Trong đó:
- scanf() là lời gọi hàm.
- format string là đối số của hàm định dạng và là một xâu chứa các tham số
định dạng. (Các đối số thông dụng sẽ đề cập trong phụ lục).
- argument_list là các biến sẽ được ghi dữ liệu vào.
Ví dụ 1 sau sẽ cho thấy cách sử dụng scanf trong một chương trình C:
#include <stdio.h>
#include <stdlib.h>

void main(){
char *ten[15];
printf("Nhap vao ten cua ban: ");
scanf("%s",ten);
printf("ban vua nhap vao | %s | bang scanf(); ",ten);
}

1.2. Tính năng không an toàn.


1.2.1. Tràn bộ đệm.
scanf(); có thể gây ra lỗi tràn bộ đệm khi được sử dụng không đúng cách, ví dụ 2 sau đây
sẽ cho thấy lỗi này xảy ra như thế nào:
#include <stdio.h>
#include <stdlib.h>

void main()
{
char ho[2], ten[2], dem[2];
printf("Nhap vao ho cua ban: ");
scanf("%s",&ho);

printf("Nhap vao ten dem cua ban: ");


scanf("%s",&dem);

printf("Nhap vao ten cua ban: ");


scanf("%s",&ten);

printf("Ho: %s\nDem: %s\nTen: %s.",ho,dem,ten);


}
3
Khi chạy chương trình, ta gặp lỗi như sau:

Lỗi ở ví dụ 2 xảy ra khi đầu vào người dùng nhập vào dài quá mức quy định của chương
trình. Để khắc phục lỗi này, cần kiểm soát đầu vào từ phía người dùng

1.2.2. Đối số %c và %s dùng cho dữ liệu kiểu char.


Khi sử dụng scanf để nhập dữ liệu dạng xâu, có thể sử dụng hai đối số %s và %c cụ thể
như sau:
- %c chỉ nhận vào một ký tự đơn.
- %s chỉ nhận vào một xâu cho đến trước dấu cách.
Việc sử dụng hai đối số này đôi khi gây ra lỗi, ví dụ 3 sẽ thể hiện rõ các lỗi như sau:
#include <stdio.h>
#include <stdlib.h>

void main()
{
char ho[2], ten[15], dem[2];
printf("Nhap vao ho cua ban: ");
scanf("%c",&ho);

printf("Nhap vao ten dem cua ban: ");


scanf("%s",&dem);

printf("Nhap vao ten cua ban: ");


scanf("%s",&ten);

printf("Ho: %s\nDem: %s\nTen: %s.",ho,dem,ten);


}
Khi tiến hành nhập đầu vào, ta có kết quả như sau:

4
Lỗi ở ví dụ 3 xảy ra vì ở hàm nhập họ, đối số %c ở scanf chỉ nhận vào một ký tự đơn duy
nhất, vì vậy ký tự u bị thừa đã bị tràn sang lưu vào biến đệm. Ở biến tên, đối số %s chỉ
lưu các xâu đơn kết thúc tại dấu cách nên không thể lưu được xâu “anh” sau dấu cách.

1.3. Giải pháp đề xuất.


1.3.1. Xử lý tràn bộ đệm.
Lỗi ở ví dụ 2 xảy ra khi đầu vào người dùng nhập vào dài quá mức quy định của chương
trình. Để khắc phục lỗi này, cần kiểm soát đầu vào từ phía người dùng. hàm fgets() được
nhắc đến ở mục “2. Gets() và các hàm liên quan.” có thể giải quyết vấn đề này.

1.3.2. Xử lý ghi xâu.


Ở ví dụ 3, để có thể ghi các xâu có nhiều ký tự và cả dấu cách, nếu vẫn sử dụng lệnh
scanf thì phải thay đổi đối số %s thành %[^\n] để ghi dữ liệu cho đến khi gặp dấu enter.
Ví dụ 4 sẽ làm sáng tỏ hơn phương pháp này:
#include <stdio.h>
#include <stdlib.h>

void main()
{
char ho[5], ten[15], dem[7];
printf("Nhap vao ho cua ban: ");
scanf("%s",&ho);

printf("Nhap vao ten dem cua ban: ");


scanf("%s",&dem);
fflush(stdin);

printf("Nhap vao ten cua ban: ");


scanf("%[^\n]",ten);

printf("Ho: %s\nDem: %s\nTen: %s.",ho,dem,ten);


}
Khởi chạy chương trình và nhập các đầu vào ta được kết quả như sau:

Ngoài ra còn có thể sử dụng lệnh gets() được nhắc tới ở mục 2 để sửa lỗi này.

2. Gets() và các hàm liên quan.


2.1. Cú pháp.
Trong C, hàm gets() được sử dụng để nhập vào các ký tự của biến dạng char. Cú pháp
của gets() có cấu trúc như sau:
5
gets(char *str)

Trong đó:
- gets là lời gọi lệnh.
- str là biên cần nhập dữ liệu vào.
Ví dụ 5 sẽ cho thấy cách sử dụng gets() trong C:
#include <stdio.h>
#include <stdlib.h>

void main()
{
char ho[5], ten[15], dem[7]
printf("Nhap vao ho cua ban: ");
gets(&ho);

printf("Nhap vao ten dem cua ban: ");


gets(&dem);

printf("Nhap vao ten cua ban: ");


gets(&ten);

printf("Ho: %s\nDem: %s\nTen: %s.",ho,dem,ten);


}
Chạy chương trình ta có kết quả sau:

2.2. Tính năng không an toàn.


gets() tuy đã khắc phục được một số vấn đề của scanf(), tuy nhiên việc thiếu kiểm soát
đầu vào vẫn có thể gây ra tràn bộ đệm. Nhập đầu vào không hợp lệ ở ví dụ 5 có thể gây
ra lỗi này:

Tràn bộ đệm khiến giá trị lưu vào các biến bị ảnh hưởng.

2.3. Giải pháp đề xuất.


Sử dụng fgets(); để kiểm soát đầu vào. Trong C, fgets() được quy định cú pháp như sau:
char *fgets(char *str, int n, FILE *stream)

6
Trong đó:
- fgets là lời gọi lệnh.
- str là biến lưu dữ liệu
- n là số nguyên dương quy định độ dài xâu nhập vào
- stream là luồng nhận dữ liệu (file hoặc bàn phím).
Ví dụ 6 sẽ thể hiện cách fgets() ngăn các đầu vào có độ dài không hợp lệ:
#include <stdio.h>
#include <stdlib.h>

void main()
{
char *ho[5], *ten[5], *dem[5];
printf("Nhap vao ho cua ban: ");
fgets(ho,5,stdin);
fflush(stdin);

printf("Nhap vao ten dem cua ban: ");


fgets(dem,5,stdin);
fflush(stdin);

printf("Nhap vao ten cua ban: ");


fgets(ten,5,stdin);
fflush(stdin);

printf("Ho: %s\nDem: %s\nTen: %s.",ho,dem,ten);


}
Các đầu vào quá dài sẽ bị cắt bớt

3. Printf() và các hàm liên quan.


3.1. Cú pháp.
Trong C, lệnh printf() dùng để in dữ liệu ra. Cú pháp của printf() được quy định trong thư
viện stdio.h như sau:
int printf(const char *format, ...)

Trong đó:
- printf là lời gọi hàm.
- char *format là biến lưu dữ liệu cần in ra.
- Ngoài ra lệnh printf có thể được sử dụng để in ra một xâu trong dấu nháy
kép “”.

7
3.2. Tính năng không an toàn.
printf() khi sử dụng không đúng cách có thể gây ra những lỗi nguy hiểm như việc in ra
địa chỉ con trỏ, tạo điều kiện cho việc thực hiện các tấn công tràn bộ đệm. Ví dụ 7 sẽ làm
rõ lỗi này:

#include <stdio.h>
#include <stdlib.h>

void main()
{
char a[10];
gets(&a);
printf(a);
}
Nhập đầu vào %p sẽ cho ra kết quả là địa chỉ các ô nhớ:

3.3. Giải pháp đề xuất.


Để tránh lỗi ở ví dụ 7, nên sử dụng print() với cú pháp đầy đủ bao gồm đối số và biến, cụ
thể như VD 8 sau:
#include <stdio.h>
#include <stdlib.h>

void main()
{
char a[10];
gets(&a);
printf("%s",a);
}
Khi nhập %p vào thì chương trình sẽ trả kết quả sau:

Lý do vì đối số %s của a nằm trong dấu nháy kép vì vậy nếu nhập %p thì printf() sẽ chỉ
in ra xâu %p thay vì địa chỉ ô nhớ.

8
CÁC THAO TÁC TRÊN XÂU

1. Hàm sao chép xâu.


1.1. Cú pháp.
Trong C, hàm dùng để sao chép xâu có cú pháp như sau:
char *strcpy(char *dest, const char *src)

Trong đó:
- -+strcpy là lời gọi hàm
- src là biến lưu xâu cần sao chép.
- dest là biến được sao chép xâu từ src vào.
Ví dụ 9 sau đây sẽ cho thấy cách strcpy() hoạt động:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
char src[15]="abcde";
char dest[15]="a";
printf("\nTruoc khi cop: \nsrc: %s\ndest: %s",src,dest);
strcpy(dest,src);
printf("\nSau khi cop\nsrc: %s\ndest: %s",src,dest);
}
Khi chương trình chạy xong, giá trị dest đã được gán giống src:

1.2. Tính năng không an toàn.


Vì strcpy() không có kiểm soát đầu vào nên có thể gây ra tràn bộ đệm khi tiến hành sao
xâu khi hai biến được cấp phát bộ nhớ khác nhau. Ví dụ 10 sau đây cho thấy rõ cách lỗi
này xảy ra:

9
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
char src[11]={'v', 'u', 'h', 'o', 'a', 'n', 'g', 'a',
'n','h'};
char dest[2]={'a'};

printf("\nTruoc khi cop: \nsrc: %s\ndest: %s",src,dest);

strcpy(dest,src);
printf("\nSau khi cop\nsrc: %s\ndest: %s",src,dest);
}
Trước khi chạy strcpy() thì giá trị lưu trong src là vuhoanganh. Nếu chương trình chạy
đúng thì sau khi kết thúc giá trị của src sẽ không đổi, tuy nhiên, do tràn bộ đệm nên dest
đã ghi đè lên phần dữ liệu của src:

1.3. Giải pháp đề xuất.


Để khắc phục lỗi dest bị tràn bộ nhớ khi sao chép xâu quá dài từ src, ta có thể sử dụng
lệnh strncpy() có cấu trúc như sau:
char *strncpy(char *dest, const char *src, size_t n)

Trong đó:
- strncpy là lời gọi lệnh.
- dest là biến sẽ sao chép xâu.
- src là biến chứa xâu cần sao chép.
- n là số nguyên quy định số xâu được phép sao chép tính từ phần tử đầu tiên
của mảng ký tự src. (Cop n-1 ký tự, để dành 1 ô nhớ cho ký hiệu kết thúc)
Ví dụ số 11 sẽ thể hiện cách hoạt động của strncpy():
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
char src[11]={'v', 'u', 'h', 'o', 'a', 'n', 'g', 'a',
'n','h'};
char dest[5]={'a'};

10
printf("\nTruoc khi cop: \nsrc: %s\ndest: %s",src,dest);

strncpy(dest,src,4);
printf("\nSau khi cop\nsrc: %s\ndest: %s",src,dest);
}
Sau khi chạy chương trình ta thấy được tràn bộ đệm đã được khắc phục:

2. Hàm nối xâu.


2.1. Cú pháp.
Trong C, hàm dùng để nối xâu có cú pháp như sau:
char *strcat(char *dest, const char *src)

Trong đó:
- strcat là lời gọi hàm.
- dest là biến chứa xâu sẽ được nối thêm từ src.
- src là biến chứa xâu sẽ nối vào đuôi dest.
Ví dụ:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
char src[11]={'v', 'u', 'h', 'o', 'a', 'n', 'g', 'a',
'n','h'};

char dest[15]={'a'};

printf("\nTruoc khi cat: \nsrc: %s\ndest: %s",src,dest);

strcat(dest,src);
printf("\nSau khi cat\nsrc: %s\ndest: %s",src,dest);
}

Khi chạy chương trình, mảng ký tự src được nối vào sau dest như sau:

11
2.2. Tính năng không an toàn.
Cũng giống như strcpy(), hàm strcat() cũng không kiểm soát được số lượng ký hiệu được
nối vào sau dest, điều này dễ dẫn tới lỗi tràn bộ đệm, ví dụ 13 sau sẽ thể hiện rõ lỗi này
ảnh hưởng tới chương trình như thế nào.
Ví dụ:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
char src[11]={'v', 'u', 'h', 'o', 'a', 'n', 'g', 'a',
'n','h'};
char dest[2]={'a'};

printf("\nTruoc khi cat: \nsrc: %s\ndest: %s",src,dest);

strcat(dest,src);
printf("\nSau khi cat\nsrc: %s\ndest: %s",src,dest);
}
Khởi chạy chương trình cho thấy việc nối thêm xâu quá dài khiến tràn bộ đệm và làm
hỏng dữ liệu lưu trên src.

2.3. Giải pháp đề xuất.


Để khắc phục lỗi dest bị tràn bộ nhớ khi nối xâu quá dài từ src, ta có thể sử dụng lệnh
strncat() có cấu trúc như sau:
char *strncat(char *dest, const char *src, size_t n)

Trong đó:
- strncat là lời gọi lệnh.
- dest là biến sẽ được nối thêm xâu.
- src là biến chứa xâu mang đi nối.

12
- n là số nguyên quy định số xâu được phép nối tính từ phần tử đầu tiên của
mảng ký tự src.
Ví dụ số 14 sẽ thể hiện cách hoạt động của strncat():
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
char src[11]={'v', 'u', 'h', 'o', 'a', 'n', 'g', 'a',
'n','h'};
char dest[6]={'a'};

printf("\nTruoc khi cop: \nsrc: %s\ndest: %s",src,dest);

strncat(dest,src,4);
printf("\nSau khi cop\nsrc: %s\ndest: %s",src,dest);
}
Sau khi chạy chương trình ta thấy được tràn bộ đệm đã được khắc phục:

13

You might also like