티스토리 뷰

반응형
Java 에서 외부 프로세스를 실행하는 기능은 주의해서 다루지 않으면 애플리케이션을 불안정하게 만든다. 해커가 해당 애플리케이션을 공격할 때 RCE가 발생할 포인트가 될 수 있기 때문이다. 

 

Java.lang.Process 클래스


외부 프로세스와의 접점인 Process 객체는 java.lang.Runtime 클래스와 java.lang.ProcessBuilder 클래스를 통해서 얻어낼 수 있다. 그에 대한 코드는 다음과 같다.

public class ProcessTest {
	public static void main(String[] args) {
    	String[] command = new String[] {"echo", "ch4njun"};
        ProcessTest.byRuntime(command);
        ProcessTest.byProcessBuilder(command);
    }
    
    public static void byRuntime(String[] command) {
    	try {
        	Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec(command);
            ProcessTest.printStream(process);
        }catch(IOException e1) {
        
        }catch(InterruptedException e2) {
        
    	}
    }
    
    public static void byProcessBuilder(String[] command) {
    	try {
        	ProcessBuilder process_builder = new ProcessBuilder(command);
            Process process = process_builder.start();
            ProcessTest.printStream(process);
        }catch(IOException e1) {
        
        }catch(InterruptedException e2) {
        
    	}
    }
    
    public static void printStream(Process process) throws IOException, InterruptedException {
    	process.waitFor();
        try (InputStream psout = process.getInputStream()) {
        	ProcessTest.copy(psout, System.out);
        }
    }
    
    public static void copy(InputStream input, OutputStream output) throws IOException, InterruptedException {
    	byte[] buffer = new byte[1024];
        int n = 0;
        while((n = input.read(buffer)) != -1) {
        	output.write(buffer, 0, n);
        }
    }
}

 

하지만 이러한 방식은 생성한 프로세스가 출력하는 메시지의 양이 많아질 경우 문제가될 수 있다.

 

위 예제에서 printStream() 메서드에서 활용한 것처럼, 하위 프로세스는 Process 객체의 getInputStream() 메서드와 getErrorStream() 메서드를 통해 출력 메시지를 부모 프로세스에게 보낼 수 있다. 이렇게 받은 스트림을 프로세스의 실행과 동시에 처리하지 않으면 서브 프로세스는 멈추거나 교착 상태에 빠질 수 있다. 플랫폼에 따라서 표준 입출력으로 데이터를 보내는 버퍼의 크기가 작을 수 있어 동시에 스트림을 읽어 제거하지 않으면 버퍼가 넘칠 수 있기 때문이다.

 

이와같은 내용이 java.lang.Process 클래스의 API 문서에도 다음과 같이 명시되어 있다.

Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, or even deadlock.

일부 네이티브 플랫폼은 표준 입력 및 출력 스트림을 위한 제한된 버퍼 크기만 제공하기 때문에, 입력 스트림을 즉시 쓰거나 하위 프로세스의 출력 스트림을 읽지 못하면 하위 프로세스가 차단되거나 심지어 교착 상태에 빠질 수 있습니다.

 

이러한 문제는 안드로이드, Python 등과 같은 다른 언어에서 외부 프로세스를 실행할 때도 동일하게 주의해줘야 하는 부분이다.

 

www.infoworld.com/article/2071275/when-runtime-exec---won-t.html

 

When Runtime.exec() won't

In this installment of Java Traps, Michael Daconta discusses one new pitfall and revisits another from his previous column. Originating in the java.lang package, the pitfall specifically involves problems with the Runtime.exec() method. Daconta also correc

www.infoworld.com

 

이러한 문제를 방지하기 위해 안전하게 스트림을 처리하도록 구현하려면 전용 클래스를 만들어야 하기 떄문에 3~4개 이상의 클래스를 작성하게 된다. 직접 구현이 번거롭다면 zt-exec 라는 오픈소스를 활용하는 것도 좋은 선택이다. 

 

zt-exec 사용법


zt-exec는 Maven Central Repository에서 사용할 수 있다. Maven 프로젝트에 다음 dependency를 추가해 사용할 수 있다.

...
<dependency>
    <groupId>org.zeroturnaround</groupId>
    <artifactId>zt-exec</artifactId>
    <version>1.12</version>
</dependency>
...

 

반환 및 출력없는 프로세스 실행

new ProcessExecutor().command("java", "-version").execute();

 

종료 코드 반환 & 출력없는 프로세스 실행

int exit = new ProcessExecutor().command("java", "-version")
                  .execute().getExitValue();

 

출력을 UTF-8 문자열로 반환하는 프로세스 실행

String output = new ProcessExecutor().command("java", "-version")
                  .readOutput(true).execute()
                  .outputUTF8();    

 

60초의 제한시간으로 프로세스 실행

try {
  new ProcessExecutor().command("java", "-version")
        .timeout(60, TimeUnit.SECONDS).execute();
}
catch (TimeoutException e) {
  // process is automatically destroyed
}

 

다른 OutputStream 으로 출력을 펌핑하는 프로세스 실행

OutputStream out = ...;
new ProcessExecutor().command("java", "-version")
      .redirectOutput(out).execute();

 

프로세스가 실행되는 동안 라인별로 출력 처리

new ProcessExecutor().command("java", "-version")
    .redirectOutput(new LogOutputStream() {
      @Override
      protected void processLine(String line) {
        ...
      }
    })
    .execute();

 

VM 이 종료될 때 실행중인 프로세스 삭제

new ProcessExecutor().command("java", "-version").destroyOnExit().execute();

 

특정 환경 변수로 프로세스 실행

new ProcessExecutor().command("java", "-version")
    .environment("foo", "bar").execute();

 

Map<String, String> env = ...
new ProcessExecutor().command("java", "-version")
    .environment(env).execute();

 

잘못된 종료 코드인 경우 예외 발생

try {
  new ProcessExecutor().command("java", "-version")
        .exitValues(3).execute();
}
catch (InvalidExitValueException e) {
  System.out.println("Process exited with " + e.getExitValue());
}

 

String output;
boolean success = false;
try {
  output = new ProcessExecutor().command("java", "-version")
                .readOutput(true).exitValues(3)
                .execute().outputUTF8();
  success = true;
}
catch (InvalidExitValueException e) {
  System.out.println("Process exited with " + e.getExitValue());
  output = e.getResult().outputUTF8();
}

 

백그라운드에서 프로세스 실행

Future<ProcessResult> future = new ProcessExecutor()
                                    .command("java", "-version")
                                    .start().getFuture();
// do some stuff
future.get(60, TimeUnit.SECONDS);

 

Future<ProcessResult> future = new ProcessExecutor()
                                    .command("java", "-version")
                                    .readOutput(true)
                                    .start().getFuture();
// do some stuff
String output = future.get(60, TimeUnit.SECONDS).outputUTF8();

 

출처


github.com/zeroturnaround/zt-exec

d2.naver.com/helloworld/1113548

 

반응형

'Back-End > Java' 카테고리의 다른 글

[JAVA] Exception vs RuntimeException  (1) 2021.11.21
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함