2011-08-20 7 views
6

Trong C, sử dụng các cuộc gọi POSIX, làm thế nào tôi có thể xác định xem đường dẫn có nằm trong thư mục đích không? Ví dụ: máy chủ web có thư mục gốc trong /srv, đây là getcwd() cho daemon. Khi phân tích cú pháp yêu cầu cho /index.html, nó trả về nội dung của /srv/index.html.Cách xác định xem đường dẫn có nằm trong thư mục không? (POSIX)

Làm cách nào để lọc các yêu cầu cho đường dẫn bên ngoài /srv?

/../etc/passwd, /valid/../../etc/passwd, , vv

Splitting con đường tại / và từ chối bất kỳ mảng chứa .. sẽ phá vỡ hợp lệ truy cập /srv/valid/../index.html.

Có cách kinh điển để thực hiện điều này với các cuộc gọi hệ thống không? Hay tôi cần phải tự đi bộ đường dẫn và đếm chiều sâu thư mục?

+2

Tôi nghĩ đây là lý do 'chroot (2)' được phát minh! –

+0

@Carl Norum: chroot tốt hơn nếu bạn cấp cho ai đó quyền truy cập shell giới hạn. nếu bạn không muốn giới hạn quyền truy cập vào một chương trình bạn tạo, có những lựa chọn tốt hơn so với chroot. – Dani

Trả lời

6

Luôn luôn realpath:

Chức năng realpath() sẽ lấy từ tên đường dẫn trỏ đến bởi * file_name *, một tên đường dẫn tuyệt đối mà giải quyết cho nhập cùng một thư mục, có độ phân giải không liên quan đến '.' , '..' hoặc liên kết tượng trưng.

Sau đó so sánh những gì realpath cung cấp cho bạn thư mục gốc bạn muốn và xem chúng có khớp không.

Bạn cũng có thể xóa tên tệp bằng tay bằng cách mở rộng các chấm kép trước khi bạn thêm "/srv". Tách đường dẫn đến trên slashes và đi qua nó từng mảnh. Nếu bạn nhận được "." thì hãy xóa nó và tiếp tục; nếu bạn nhận được một "..", sau đó loại bỏ nó và các thành phần trước đó (chăm sóc không đi qua các mục đầu tiên trong danh sách của bạn); nếu bạn nhận được bất cứ điều gì khác, chỉ cần chuyển sang thành phần tiếp theo. Sau đó dán những gì còn lại cùng với các dấu gạch chéo giữa các thành phần và thêm "/srv/" của bạn. Vì vậy, nếu ai đó cung cấp cho bạn "/valid/../../etc/passwd", bạn sẽ kết thúc với "/srv/etc/passwd""/where/is/../pancakes/house" sẽ kết thúc là "/srv/where/pancakes/house".

Bằng cách đó bạn không thể ra ngoài "/srv" (ngoại trừ thông qua các liên kết tượng trưng) và "/../.." đến sẽ giống như "/" (giống như trong hệ thống tệp thông thường). Nhưng bạn vẫn muốn sử dụng realpath nếu bạn lo lắng về biểu tượng dưới "/srv".

Làm việc với thành phần tên đường dẫn theo thành phần cũng sẽ cho phép bạn ngắt kết nối giữa bố cục bạn trình bày với thế giới bên ngoài và bố cục hệ thống tệp thực; không cần "/this/that/other/thing" để ánh xạ tới tệp thực tế "/srv/this/that/other/thing" ở bất kỳ đâu, đường dẫn có thể chỉ là một khóa trong một số loại cơ sở dữ liệu hoặc một số loại đường dẫn không gian tên cho một cuộc gọi hàm.

0

Bạn chỉ cần tự mình xử lý .. và xóa thành phần đường dẫn trước đó khi nó được tìm thấy, do đó không có sự xuất hiện của .. trong chuỗi cuối cùng bạn sử dụng để mở tệp.

2

Để xác định xem tệp F có nằm trong thư mục D, trước tiên là D để xác định số thiết bị và số inode (thành viên st_dev và st_ino của struct stat).

Sau đó, chỉ số F để xác định xem đó có phải là một thư mục hay không. Nếu không, hãy gọi tên cơ sở để xác định tên của thư mục chứa nó. Đặt G thành tên của thư mục này. Nếu F đã là một thư mục, hãy đặt G = F.

Bây giờ, F nằm trong D nếu và chỉ khi G nằm trong D. Tiếp theo chúng ta có vòng lặp.

while (1) { 
    if (samefile(d_statinfo.d_dev, d_statinfo.d_ino, G)) { 
    return 1; // F was within D 
    } else if (0 == strcmp("/", G) { 
    return 0; // F was not within D. 
    } 
    G = dirname(G); 
} 

Chức năng samefile rất đơn giản:

int samefile(dev_t ddev, ino_t dino, const char *path) { 
    struct stat st; 
    if (0 == stat(path, &st)) { 
    return ddev == st.st_dev && dino == st.st_no; 
    } else { 
    throw ...; // or return error value (but also change the caller to detect it) 
    } 
} 

này sẽ làm việc trên hệ thống tập tin POSIX. Nhưng nhiều hệ thống tập tin không phải là POSIX. Các vấn đề cần tìm kiếm bao gồm:

  1. Hệ thống tệp nơi thiết bị/inode không phải là duy nhất. Một số hệ thống tập tin FUSE là những ví dụ về điều này; đôi khi chúng tạo nên số inode khi các hệ thống tệp cơ bản không có chúng. Họ không nên sử dụng lại số inode, nhưng một số hệ thống tập tin FUSE có lỗi.
  2. Triển khai NFS bị hỏng. Trên một số hệ thống, tất cả các hệ thống tệp NFS có cùng số thiết bị. Nếu chúng vượt qua số inode như nó tồn tại trên máy chủ, điều này có thể gây ra một vấn đề (mặc dù tôi chưa bao giờ thấy nó xảy ra trong thực tế).
  3. Linux ràng buộc các điểm gắn kết. Nếu /a là gắn kết gắn kết của /b, thì /a/1 có vẻ như nằm bên trong /a, nhưng với việc triển khai ở trên, /b/1 cũng xuất hiện bên trong /a. Tôi nghĩ đó có lẽ là câu trả lời đúng. Tuy nhiên, nếu đây không phải là kết quả bạn thích, điều này có thể dễ dàng được sửa bằng cách thay đổi trường hợp return 1 để gọi số strcmp() để so sánh tên đường dẫn. Tuy nhiên, để làm việc này, bạn sẽ cần phải bắt đầu bằng cách gọi số realpath trên cả F và D. Cuộc gọi realpath có thể khá tốn kém (vì có thể cần phải truy cập đĩa nhiều lần).
  4. Đường dẫn đặc biệt //foo/bar. POSIX cho phép tên đường dẫn bắt đầu bằng // là đặc biệt theo cách không được xác định rõ ràng. Thực ra tôi quên mức độ bảo đảm chính xác về ngữ nghĩa mà POSIX cung cấp. Tôi nghĩ rằng POSIX cho phép //foo/bar//baz/ugh để chỉ cùng một tệp. Kiểm tra thiết bị/inode vẫn nên làm điều phù hợp với bạn nhưng bạn có thể không tìm thấy nó (tức là bạn có thể thấy rằng //foo/bar//baz/ugh có thể tham chiếu cùng một tệp nhưng có số thiết bị/số inode khác nhau).

câu trả lời này giả định rằng chúng ta bắt đầu với một đường dẫn tuyệt đối cho cả F và D. Nếu điều này không được bảo đảm bạn có thể cần phải làm một số chuyển đổi sử dụng realpath()getcwd(). Đây sẽ là một vấn đề nếu tên của thư mục hiện tại dài hơn PATH_MAX (điều này chắc chắn có thể xảy ra).