Đâu là ngã ba () trên bom ngã ba: () {: |: &};:?


25

Cảnh báo: Chạy lệnh này trong hầu hết các shell sẽ dẫn đến một hệ thống bị hỏng sẽ cần phải tắt máy để sửa chữa

Tôi hiểu chức năng đệ quy :(){ :|: & };:và những gì nó làm. Nhưng tôi không biết cuộc gọi hệ thống ngã ba ở đâu. Tôi không chắc chắn, nhưng tôi nghi ngờ trong đường ống |.


Câu trả lời:


30

Kết quả của đường ống trong x | y, một lớp con được tạo ra để chứa đường ống là một phần của nhóm quy trình tiền cảnh. Điều này tiếp tục tạo ra các lớp con (thông qua fork()) vô thời hạn, do đó tạo ra một quả bom ngã ba.

$ for (( i=0; i<3; i++ )); do
>     echo "$BASHPID"
> done
16907
16907
16907
$ for (( i=0; i<3; i++ )); do
>     echo "$BASHPID" | cat
> done
17195
17197
17199

Ngã ba không thực sự xảy ra cho đến khi mã được chạy, tuy nhiên, đó là lời gọi cuối cùng :trong mã của bạn.

Để tháo rời cách thức hoạt động của bom ngã ba:

  • :() - xác định một hàm mới gọi là :
  • { :|: & } - một định nghĩa hàm đệ quy chức năng gọi hàm vào một thể hiện khác của chức năng gọi trong nền
  • : - gọi chức năng bom ngã ba

Điều này có xu hướng không quá nhiều bộ nhớ, nhưng nó sẽ hút các bộ vi xử lý và tiêu thụ chu kỳ CPU.


Trong x | y, tại sao có một vỏ phụ được tạo ra? Theo hiểu biết của tôi, khi bash thấy a pipe, nó thực hiện pipe()cuộc gọi hệ thống, trả về hai fds. Bây giờ, lệnh_left là execed và đầu ra được đưa vào lệnh_right làm đầu vào. Bây giờ, lệnh_right là execed. Vậy, tại sao BASHPIDmỗi lần lại khác nhau?
Abhijeet Rastogi

2
@shadyabhi Thật đơn giản - xylà 2 lệnh riêng biệt chạy trong 2 quy trình riêng biệt, do đó bạn có 2 chuỗi con riêng biệt. Nếu xchạy trong cùng tiến trình với shell, điều đó có nghĩa là xphải được tích hợp sẵn.
jw013

24

Bit cuối cùng của mã, ;:đang chạy hàm :(){ ... }. Đây là nơi xảy ra ngã ba.

Dấu chấm phẩy chấm dứt lệnh đầu tiên và chúng ta đang bắt đầu một lệnh khác, tức là gọi hàm :. Định nghĩa của hàm này bao gồm một lệnh gọi đến chính nó ( :) và đầu ra của lệnh gọi này được chuyển sang phiên bản nền :. Đạo cụ này lên quá trình vô thời hạn.

Mỗi khi bạn đang gọi điện thoại chức năng :()bạn đang gọi hàm C fork(). Cuối cùng, điều này sẽ làm cạn kiệt tất cả các ID quá trình (PID) trên hệ thống.

Thí dụ

Bạn có thể trao đổi |:&với một cái gì đó khác để bạn có thể biết được chuyện gì đang xảy ra.

Thiết lập trình theo dõi

Trong một cửa sổ đầu cuối làm điều này:

$ watch "ps -eaf|grep \"[s]leep 61\""

Cài đặt bom ngã ba "cầu chì bị trì hoãn"

Trong một cửa sổ khác, chúng tôi sẽ chạy một phiên bản sửa đổi một chút của bom ngã ba. Phiên bản này sẽ cố gắng điều tiết để chúng ta có thể nghiên cứu những gì nó đang làm. Phiên bản của chúng tôi sẽ ngủ trong 61 giây trước khi gọi chức năng :().

Ngoài ra, chúng tôi cũng sẽ thực hiện cuộc gọi ban đầu, sau khi nó được gọi. Ctrl+ z, sau đó gõ bg.

$ :(){ sleep 61; : | : & };:

# control + z
[1]+  Stopped                 sleep 61
[2] 5845
$ bg
[1]+ sleep 61 &

Bây giờ nếu chúng ta chạy jobslệnh trong cửa sổ ban đầu, chúng ta sẽ thấy điều này:

$ jobs
[1]-  Running                 sleep 61 &
[2]+  Running                 : | : &

Sau một vài phút:

$ jobs
[1]-  Done                    sleep 61
[2]+  Done                    : | :

Kiểm tra với người theo dõi

Trong khi đó ở cửa sổ khác nơi chúng tôi đang chạy watch:

Every 2.0s: ps -eaf|grep "[s]leep 61"                                                                                                                                             Sat Aug 31 12:48:14 2013

saml      6112  6108  0 12:47 pts/2    00:00:00 sleep 61
saml      6115  6110  0 12:47 pts/2    00:00:00 sleep 61
saml      6116  6111  0 12:47 pts/2    00:00:00 sleep 61
saml      6117  6109  0 12:47 pts/2    00:00:00 sleep 61
saml      6119  6114  0 12:47 pts/2    00:00:00 sleep 61
saml      6120  6113  0 12:47 pts/2    00:00:00 sleep 61
saml      6122  6118  0 12:47 pts/2    00:00:00 sleep 61
saml      6123  6121  0 12:47 pts/2    00:00:00 sleep 61

Quy trình phân cấp

Và một ps -auxfhiển thị phân cấp quá trình này:

$ ps -auxf
saml      6245  0.0  0.0 115184  5316 pts/2    S    12:48   0:00 bash
saml      6247  0.0  0.0 100988   468 pts/2    S    12:48   0:00  \_ sleep 61
....
....
saml      6250  0.0  0.0 115184  5328 pts/2    S    12:48   0:00 bash
saml      6268  0.0  0.0 100988   468 pts/2    S    12:48   0:00  \_ sleep 61
saml      6251  0.0  0.0 115184  5320 pts/2    S    12:48   0:00 bash
saml      6272  0.0  0.0 100988   468 pts/2    S    12:48   0:00  \_ sleep 61
saml      6252  0.0  0.0 115184  5324 pts/2    S    12:48   0:00 bash
saml      6269  0.0  0.0 100988   464 pts/2    S    12:48   0:00  \_ sleep 61
...
...

Dọn dẹp thời gian

A killall bashsẽ dừng mọi thứ trước khi chúng ra khỏi tầm tay. Làm sạch bạn theo cách này có thể hơi nặng tay, một cách nhẹ nhàng hơn mà không có khả năng xé mọi bashvỏ sò, sẽ là làm như sau:

  1. Xác định thiết bị đầu cuối giả nào mà bom ngã ba sẽ chạy trong

    $ tty
    /dev/pts/4
  2. Giết thiết bị đầu cuối giả

    $ pkill -t pts/4

Vì vậy những gì đang xảy ra?

Vâng, mỗi lần gọi bashsleeplà một lệnh gọi hàm C fork()từ trình bashbao nơi lệnh được chạy.


7
bashcó thể đang chạy trên các thiết bị đầu cuối riêng biệt. Tốt hơn là sử dụng pkill -t pts/2.
Maciej Piechotka

@MaciejPiechotka - cảm ơn vì tiền boa. Chưa bao giờ thấy cái đó trước đây, tôi đã thêm nó vào câu trả lời!
slm
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.