Có phải là một ý tưởng tốt để gọi các lệnh shell từ bên trong C?


50

Có một lệnh shell unix ( udevadm info -q path -n /dev/ttyUSB2) mà tôi muốn gọi từ chương trình C. Có lẽ khoảng một tuần đấu tranh, tôi có thể tự mình thực hiện lại, nhưng tôi không muốn làm điều đó.

Là nó được chấp nhận rộng rãi thực tiễn tốt cho tôi chỉ để gọi popen("my_command", "r");, hoặc sẽ giới thiệu các vấn đề bảo mật không thể chấp nhận và chuyển tiếp các vấn đề tương thích? Tôi cảm thấy sai khi làm điều gì đó như thế này, nhưng tôi không thể đặt ngón tay của mình vào lý do tại sao nó sẽ xấu.


8
Một điều cần xem xét là các cuộc tấn công tiêm.
MetaFight

8
Tôi chủ yếu nghĩ về trường hợp bạn cung cấp đầu vào của người dùng dưới dạng đối số lệnh shell.
MetaFight

8
Ai đó có thể đặt một udevadmtệp thực thi độc hại trong cùng thư mục với chương trình của bạn và sử dụng nó để leo thang các đặc quyền không? Tôi không biết nhiều về bảo mật unix, nhưng chương trình của bạn có được chạy setuid rootkhông?
Andrew Piliser

7
@AndrewPiliser Nếu thư mục hiện tại không có PATH, thì không - nó sẽ phải được chạy cùng ./udevadm. Mặc dù vậy, IIRC Windows sẽ chạy các thư mục thực thi cùng thư mục.
Izkata

4
Bất cứ khi nào có thể gọi chương trình mong muốn trực tiếp mà không cần thông qua shell.
CodeInChaos

Câu trả lời:


58

Nó không đặc biệt xấu, nhưng có một số cảnh báo.

  1. Giải pháp di động của bạn sẽ như thế nào? Nhị phân được chọn của bạn sẽ hoạt động giống nhau ở mọi nơi, xuất kết quả theo cùng định dạng, v.v.? Nó sẽ xuất khác nhau trên các thiết lập của LANGvv?
  2. Bao nhiêu phụ tải này thêm vào quá trình của bạn? Ngã ba kết quả nhị phân trong tải nhiều hơn và đòi hỏi nhiều tài nguyên hơn so với thực hiện các cuộc gọi thư viện (nói chung). Điều này có thể chấp nhận được trong kịch bản của bạn?
  3. Có vấn đề bảo mật? Ai đó có thể thay thế nhị phân đã chọn của bạn bằng một thứ khác và thực hiện những hành động bất chính sau đó không? Bạn có sử dụng các đối số do người dùng cung cấp cho nhị phân của mình không và họ có thể cung cấp ;rm -rf /(ví dụ) (lưu ý rằng một số API sẽ cho phép bạn chỉ định các đối số an toàn hơn là chỉ cung cấp chúng trên dòng lệnh)

Nói chung tôi rất vui khi thực hiện các tệp nhị phân khi tôi ở trong một môi trường đã biết mà tôi có thể dự đoán, khi đầu ra nhị phân dễ phân tích (nếu được yêu cầu - bạn có thể chỉ cần một mã thoát) và tôi cũng không cần phải làm điều đó thường xuyên

Như bạn đã lưu ý, vấn đề khác là cần bao nhiêu công việc để nhân rộng những gì nhị phân làm? Nó có sử dụng một thư viện mà bạn cũng có thể tận dụng?


13
Forking a binary results in a lot more load and requires more resources than executing library calls (generally speaking). Nếu điều này là trên Windows, tôi sẽ đồng ý. Tuy nhiên, OP chỉ định Unix trong đó forking có chi phí đủ thấp đến mức khó có thể nói liệu việc tải động một thư viện có tệ hơn so với forking hay không.
wallyk

1
Tôi thường mong đợi một thư viện sẽ được tải một lần , trong khi nhị phân có thể được thực thi nhiều lần. Như mọi khi, nó phụ thuộc vào các kịch bản sử dụng, nhưng cần được xem xét (nếu sau đó bị loại bỏ)
Brian Agnew

3
Không có "forking" trên Windows, do đó báo giá là Unix-cụ thể.
Cody Grey

5
Nhân NT không hỗ trợ forking, mặc dù nó không được hiển thị thông qua Win32. Dù sao, tôi sẽ tin rằng chi phí để chạy một chương trình bên ngoài sẽ đến từ execnhiều hơn là từ fork. Đang tải các phần, liên kết trong các đối tượng được chia sẻ, chạy mã init cho mỗi đối tượng. Và sau đó phải chuyển đổi tất cả dữ liệu thành văn bản để được phân tích cú pháp ở phía bên kia, cho cả đầu vào và đầu ra.
quang phổ

8
Công bằng mà nói, udevadm info -q path -n /dev/ttyUSB2dù sao cũng sẽ không hoạt động trên Windows nên toàn bộ cuộc thảo luận này là một chút lý thuyết.
MSalters

37

Cần hết sức cẩn thận để bảo vệ chống lại các lỗ hổng tiêm khi bạn đã giới thiệu một vectơ tiềm năng. Bây giờ bạn đang ở trong tâm trí của bạn, nhưng sau này bạn có thể cần khả năng lựa chọn ttyUSB0-3, sau đó danh sách đó sẽ được sử dụng ở những nơi khác để nó được thực hiện theo nguyên tắc trách nhiệm duy nhất, sau đó khách hàng sẽ có yêu cầu đặt một thiết bị tùy ý trong danh sách và nhà phát triển thực hiện thay đổi đó sẽ không biết rằng cuối cùng danh sách đó sẽ được sử dụng theo cách không an toàn.

Nói cách khác, mã như thể nhà phát triển bất cẩn nhất mà bạn biết đang thực hiện một thay đổi không an toàn trong một phần dường như không liên quan của mã.

Thứ hai, đầu ra của các công cụ CLI thường không được coi là giao diện ổn định trừ khi tài liệu đặc biệt đánh dấu chúng như vậy. Bạn có thể ổn khi tin tưởng vào kịch bản bạn chạy mà bạn có thể tự khắc phục sự cố, nhưng không phải là thứ bạn triển khai cho khách hàng.

Thứ ba, nếu bạn muốn một cách dễ dàng để trích xuất một giá trị từ phần mềm của mình, rất có thể ai đó cũng muốn nó. Hãy tìm một thư viện đã làm những gì bạn muốn. libudevđã được cài đặt trên hệ thống của tôi:

#include <libudev.h>
#include <sys/stat.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
    struct stat statbuf;

    if (stat("dev/ttyUSB2", &statbuf) < 0)
        return -1;
    struct udev* udev = udev_new();
    struct udev_device *dev = udev_device_new_from_devnum(udev, 'c', statbuf.st_rdev);

    printf("%s\n", udev_device_get_devpath(dev));

    udev_device_unref(dev);
    udev_unref(udev);
    return 0;
}

Có chức năng hữu ích khác trong thư viện đó. Tôi đoán là nếu bạn cần điều này, một số chức năng liên quan cũng có thể có ích.


2
CẢM ƠN BẠN. Tôi đã dành quá lâu để tìm libudev. Tôi không thể tìm thấy nó!
johnny_boy

2
Tôi không thấy "lỗ hổng tiêm chích" là một vấn đề ở đây trừ khi chương trình của OP được gọi khi một người dùng khác kiểm soát môi trường của nó, chủ yếu là trong trường hợp suid (cũng có thể, nhưng ở mức độ thấp hơn, những thứ như cgi và ssh bắt buộc-lệnh). Không có lý do gì các chương trình bình thường cần phải được tăng cường đối với người dùng mà họ đang chạy.
R ..

2
@R ..: "Lỗ hổng tiêm" là khi bạn khái quát hóa ttyUSB2và sử dụng sprintf(buf, "udevadm info -q path -n /dev/%s", argv[1]). Bây giờ có vẻ khá an toàn, nhưng nếu argv[1]"nul || echo OOPS"gì?
MSalters

3
@R ..: bạn chỉ cần thêm trí tưởng tượng. Đầu tiên, đó là một chuỗi cố định, sau đó là một đối số được cung cấp bởi người dùng, sau đó là một đối số được cung cấp bởi một số chương trình khác do người dùng điều hành nhưng họ không hiểu, sau đó là một đối số mà trình duyệt của họ đã trích xuất từ ​​một trang web. Nếu mọi thứ cứ tiếp tục "xuống dốc", thì cuối cùng cũng có người phải chịu trách nhiệm vệ sinh đầu vào, và Karl đang nói rằng cần hết sức cẩn thận để xác định điểm đó, bất cứ nơi nào nó có thể đến.
Steve Jessop

6
Mặc dù nếu nguyên tắc phòng ngừa thực sự là "giả sử nhà phát triển bất cẩn nhất mà bạn biết đang thực hiện thay đổi không an toàn", và tôi phải viết mã ngay bây giờ để đảm bảo rằng không thể dẫn đến việc khai thác, nhưng cá nhân tôi không thể rời khỏi giường. Các nhà phát triển bất cẩn nhất mà tôi biết làm cho Machievelli trông giống như anh ta thậm chí không cố gắng .
Steve Jessop

16

Trong trường hợp cụ thể của bạn, nơi bạn muốn gọi udevadm, tôi nghi ngờ bạn có thể truy cập udevnhư một thư viện và thực hiện các cuộc gọi chức năng phù hợp thay thế?

ví dụ: bạn có thể xem chính udevadm đang làm gì khi gọi trong chế độ "thông tin": https://github.com/gentoo/eudev/blob/master/src/udev/udevadm-info.c và thực hiện các cuộc gọi Equiv như những udevadm đang làm.

Điều này sẽ tránh được rất nhiều sự đánh đổi nhược điểm được liệt kê trong câu trả lời xuất sắc của Brian Agnew - ví dụ, không dựa vào nhị phân tồn tại ở một con đường nhất định, tránh chi phí cho việc giả mạo, v.v.


Cảm ơn, tôi đã tìm thấy repo chính xác đó, và cuối cùng tôi đã xem qua nó một chút.
johnny_boy

1
Sự khác biệt và libudev không khó sử dụng. Việc xử lý lỗi của nó hơi thiếu, nhưng việc liệt kê, truy vấn và giám sát API khá đơn giản. Cuộc đấu tranh mà nỗi sợ hãi của bạn có thể xuất hiện dưới 2 giờ nếu bạn thử.
quang phổ

7

Câu hỏi của bạn dường như kêu gọi một câu trả lời trong rừng và câu trả lời ở đây có vẻ giống như câu trả lời của cây, vì vậy tôi nghĩ rằng tôi sẽ cho bạn một câu trả lời về rừng.

Điều này rất hiếm khi các chương trình C được viết. Nó luôn luôn là cách các kịch bản shell được viết và đôi khi các chương trình Python, perl hoặc Ruby được viết như thế nào.

Mọi người thường viết bằng C để dễ dàng sử dụng các thư viện hệ thống và truy cập trực tiếp ở mức độ thấp vào các cuộc gọi hệ điều hành cũng như tốc độ. Và C là một ngôn ngữ khó viết, vì vậy nếu mọi người không cần những thứ đó, thì họ không sử dụng C. Ngoài ra, các chương trình C thường được dự kiến ​​chỉ có phụ thuộc vào thư viện chia sẻ và tệp cấu hình.

Tách ra một quy trình phụ không đặc biệt nhanh và nó không yêu cầu quyền truy cập chi tiết và có kiểm soát vào các cơ sở hệ thống cấp thấp, và nó đưa ra một sự phụ thuộc đáng ngạc nhiên vào một thực thi bên ngoài, vì vậy rất hiếm khi thấy trong các chương trình C.

Có một số mối quan tâm bổ sung. Các mối quan tâm về bảo mật và tính di động mà mọi người đề cập là hoàn toàn hợp lệ. Tất nhiên chúng có giá trị như nhau đối với tập lệnh shell, nhưng mọi người đang mong đợi những loại vấn đề đó trong tập lệnh shell. Nhưng các chương trình C thường không có mối quan tâm bảo mật này, điều này làm cho nó nguy hiểm hơn.

Nhưng, theo tôi, mối quan tâm lớn nhất liên quan đến cách thức popensẽ tương tác với phần còn lại của chương trình của bạn. popenphải tạo một tiến trình con, đọc đầu ra của nó và thu thập trạng thái thoát của nó. Trong khi đó, stderr của quá trình đó sẽ được kết nối với stderr tương tự như chương trình của bạn, điều này có thể gây ra đầu ra khó hiểu và stdin của nó sẽ giống như chương trình của bạn, điều này có thể gây ra các vấn đề thú vị khác. Bạn có thể giải quyết điều đó bằng cách đưa </dev/null 2>/dev/nullvào chuỗi bạn chuyển đến popenvì nó được giải thích bởi trình bao.

popentạo ra một quá trình con. Nếu bạn làm bất cứ điều gì với việc xử lý tín hiệu hoặc tự xử lý quá trình, bạn có thể sẽ nhận được SIGCHLDtín hiệu kỳ lạ . Các cuộc gọi của bạn waitcó thể tương tác kỳ lạ với popenvà có thể tạo ra các điều kiện cuộc đua kỳ lạ.

Tất nhiên, mối quan tâm về bảo mật và tính di động là có. Vì chúng dành cho các kịch bản shell hoặc bất cứ thứ gì khởi động các tệp thực thi khác trên hệ thống. Và bạn phải cẩn thận mà mọi người sử dụng chương trình của bạn không thể nhận được vỏ meta-charcaters vào chuỗi bạn vượt qua thành popenvì chuỗi được đưa trực tiếp đến shvới sh -c <string from popen as a single argument>.

Nhưng tôi không nghĩ họ là lý do tại sao thật lạ khi thấy một chương trình C sử dụng popen. Lý do nó lạ là vì C thường là ngôn ngữ cấp thấp và popenkhông phải là cấp độ thấp. Và bởi vì việc sử dụng các popenràng buộc thiết kế vị trí trên chương trình của bạn bởi vì nó sẽ tương tác kỳ lạ với đầu vào và đầu ra tiêu chuẩn của chương trình của bạn và khiến cho việc quản lý quy trình hoặc xử lý tín hiệu của bạn trở nên khó khăn. Và bởi vì các chương trình C thường không được dự kiến ​​sẽ có sự phụ thuộc vào các tệp thực thi bên ngoài.


0

Chương trình của bạn có thể bị hack, v.v ... Một cách để tự bảo vệ bạn khỏi loại hoạt động này là tạo một bản sao của môi trường máy hiện tại của bạn và chạy chương trình của bạn bằng một hệ thống gọi là chroot.

Từ quan điểm của chương trình của bạn, nó thực thi trong một môi trường bình thường, từ khía cạnh bảo mật, nếu ai đó phá vỡ chương trình của bạn, nó chỉ có quyền truy cập vào các yếu tố bạn cung cấp khi bạn tạo bản sao.

Một thiết lập như vậy được gọi là nhà tù chroot để biết thêm chi tiết xem nhà tù chroot .

Nó thường được sử dụng để thiết lập các máy chủ có thể truy cập công khai, vv


3
OP đang viết một chương trình cho người khác chạy, không chơi với các tệp nhị phân không đáng tin cậy hoặc nguồn trên hệ thống của chính anh ta.
Peter Cordes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.