Java Security Coding Guide: File IO Operation

Introduction

For file IO operations, we should often use them. Because of the complexity of files, there are many things we need to pay attention to when using File operations. Let me take a look.

Specify the appropriate permissions when creating the file

Regardless of whether it is in windows or linux, files have the concept of permission control. We can set the owner of the file and the permission of the file. If the file permission is not well controlled, malicious users may carry out malicious operations on our files.

So we need to consider the issue of permissions when creating files.

Unfortunately, Java is not good at file operations, so before JDK1.6, Java's IO operations were very weak. Basic file operation classes, such as FileOutputStream and FileWriter, did not have permission options.

Writer out = new FileWriter("file");

So how to deal with it?

Before JDK1.6, we needed to use some local methods to implement the permission modification function.

After JDK1.6, Java introduced NIO, which can control file permissions through some features of NIO.

Let's take a look at the createFile method of the Files tool class:

    public static Path createFile(Path path, FileAttribute<?>... attrs)
        throws IOException
    {
    
    
        newByteChannel(path, DEFAULT_CREATE_OPTIONS, attrs).close();
        return path;
    }

Among them, FileAttribute is the attribute of the file. Let's take a look at how to specify file permissions:

    public void createFileWithPermission() throws IOException {
    
    
        Set<PosixFilePermission> perms =
                PosixFilePermissions.fromString("rw-------");
        FileAttribute<Set<PosixFilePermission>> attr =
                PosixFilePermissions.asFileAttribute(perms);
        Path file = new File("/tmp/www.flydean.com").toPath();
        Files.createFile(file,attr);
    }

Pay attention to check the return value of the file operation

Many file operations in java have return values, such as file.delete(). We need to judge whether the file operation is complete based on the return value, so don't ignore the return value.

Delete temporary files after use

If we use files that do not need permanent storage, we can easily use File's createTempFile to create temporary files. The name of the temporary file is randomly generated, and we hope to delete it after the temporary file is used up.

How to delete it? File provides a deleteOnExit method, which deletes the file when the JVM exits.

Note that the JVM here must exit normally, if it exits abnormally, the file will not be deleted.

Let's look at the following example:

    public void wrongDelete() throws IOException {
    
    
        File f = File.createTempFile("tmpfile",".tmp");
        FileOutputStream fop = null;
        try {
    
    
            fop = new FileOutputStream(f);
            String str = "Data";
            fop.write(str.getBytes());
            fop.flush();
        } finally {
    
    
            // 因为Stream没有被关闭,所以文件在windows平台上面不会被删除
            f.deleteOnExit(); // 在JVM退出的时候删除临时文件

            if (fop != null) {
    
    
                try {
    
    
                    fop.close();
                } catch (IOException x) {
    
    
                    // Handle error
                }
            }
        }
    }

In the above example, we created a temporary file and called the deleteOnExit method in finally, but because the stream was not closed when the method was called, the file was not deleted on the windows platform.

How to solve it?

NIO provides a DELETE_ON_CLOSE option to ensure that the file is deleted after it is closed:

    public void correctDelete() throws IOException {
    
    
        Path tempFile = null;
            tempFile = Files.createTempFile("tmpfile", ".tmp");
            try (BufferedWriter writer =
                         Files.newBufferedWriter(tempFile, Charset.forName("UTF8"),
                                 StandardOpenOption.DELETE_ON_CLOSE)) {
    
    
                // Write to file
            }
        }

In the above example, we added StandardOpenOption.DELETE_ON_CLOSE during the creation of the writer, then the file will be deleted after the writer is closed.

Release resources that are no longer used

If resources are no longer used, we need to remember to close them, otherwise it will cause resource leakage.

But many times we may forget to close, so what should we do? The try-with-resources mechanism is introduced in JDK7. As long as the resource that implements the Closeable interface is placed in the try statement, it will be automatically closed, which is very convenient.

Pay attention to the safety of Buffer

NIO provides many very useful Buffer classes, such as IntBuffer, CharBuffer and ByteBuffer. These Buffers actually encapsulate the underlying array. Although a new Buffer object is created, this Buffer is associated with the underlying array. , So don't expose the Buffer easily, otherwise the underlying array may be modified.

    public CharBuffer getBuffer(){
    
    
         char[] dataArray = new char[10];
         return CharBuffer.wrap(dataArray);
    }

The above example exposes CharBuffer, in fact it also exposes the underlying char array.

There are two ways to improve it:

    public CharBuffer getBuffer1(){
    
    
        char[] dataArray = new char[10];
        return CharBuffer.wrap(dataArray).asReadOnlyBuffer();
    }

The first way is to convert CharBuffer to read-only.

The second way is to create a new Buffer and cut off the connection between the Buffer and the array:

    public CharBuffer getBuffer2(){
    
    
        char[] dataArray = new char[10];
        CharBuffer cb = CharBuffer.allocate(dataArray.length);
        cb.put(dataArray);
        return cb;
    }

Pay attention to the standard input and output of Process

In Java, you can execute native commands through Runtime.exec(), and Runtime.exec() has a return value, and its return value is a Process object used to control and obtain execution information of the native program.

By default, the created Process does not have its own I/O stream, which means that the Process uses the parent process I/O (stdin, stdout, stderr), and Process provides the following three methods to obtain I/O:

getOutputStream()
getInputStream()
getErrorStream()

If it is IO using the parent process, then on some systems, these buffers are relatively small. If there are a large number of input and output operations, it may be blocked or even deadlock.

How to do it? What we have to do is to process the IO generated by the Process to prevent the blocking of the Buffer.

public class StreamProcesser implements Runnable{
    
    
    private final InputStream is;
    private final PrintStream os;

    StreamProcesser(InputStream is, PrintStream os){
    
    
        this.is=is;
        this.os=os;
    }

    @Override
    public void run() {
    
    
        try {
    
    
            int c;
            while ((c = is.read()) != -1)
                os.print((char) c);
        } catch (IOException x) {
    
    
            // Handle error
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
    
    
        Runtime rt = Runtime.getRuntime();
        Process proc = rt.exec("vscode");

        Thread errorGobbler
                = new Thread(new StreamProcesser(proc.getErrorStream(), System.err));

        Thread outputGobbler
                = new Thread(new StreamProcesser(proc.getInputStream(), System.out));

        errorGobbler.start();
        outputGobbler.start();

        int exitVal = proc.waitFor();
        errorGobbler.join();
        outputGobbler.join();
    }
}

In the above example, we created a StreamProcesser to handle Process Error and Input.

InputStream.read() and Reader.read()

Both InputStream and Reader have a read() method. The difference between these two methods is that InputStream reads Byte, while Reader reads char.

Although the range of Byte is -128 to 127, InputStream.read() will convert the read Byte into an int in the range of 0-255 (0x00-0xff).

The range of Char is 0x0000-0xffff, Reader.read() will return the int value in the same range: 0x0000-0xffff.

If the return value is -1, it means that the Stream is over. The int of -1 here is: 0xffffffff.

In the process of using, we need to judge the return value read to distinguish the boundary of the Stream.

We consider this question:

FileInputStream in;
byte data;
while ((data = (byte) in.read()) != -1) {
    
    
}

Above we convert the read result of InputStream to byte first, and then judge whether it is equal to -1. What's the problem?

If the value of Byte itself is 0xff, which is a -1, but after the InputStream is read, it is converted into an int in the range of 0-255, then the int value after conversion is: 0x000000FF, and the byte conversion is performed again, which will be intercepted The final Oxff, Oxff == -1, eventually leads to an incorrect judgment that the Stream ends.

So we need to judge the return value first, and then perform the conversion:

FileInputStream in;
int inbuff;
byte data;
while ((inbuff = in.read()) != -1) {
    
    
  data = (byte) inbuff;
  // ... 
}

Further reading:

What is the output of this code? (int)(char)(byte)-1

First, -1 is converted to byte: -1 is 0xffffffff, and converted to byte, the last few bits are directly intercepted to get 0xff, which is -1.

Then byte is converted to char: 0xff byte is signed, and converted to a 2-byte char requires sign bit extension to become 0xffff, but char is unsigned, and the corresponding decimal is 65535.

Finally, char is converted to int. Because char is unsigned, it expands to 0x0000ffff, and the corresponding decimal number is 65535.

Similarly, in the following example, if char is used to convert int in advance, because the range of char is unsigned, it can never be equal to -1.

FileReader in;
char data;
while ((data = (char) in.read()) != -1) {
    
    
  // ...
}

The write() method should not exceed the scope

There is a very strange method in OutputStream, which is write. Let's look at the definition of write method:

    public abstract void write(int b) throws IOException;

write receives an int parameter, but actually writes a byte.

Because the range of int and byte are not the same, the incoming int will be truncated and converted into a byte.

So we must determine the scope of writing when using:

    public void writeInt(int value){
    
    
        int intValue = Integer.valueOf(value);
        if (intValue < 0 || intValue > 255) {
    
    
            throw new ArithmeticException("Value超出范围");
        }
        System.out.write(value);
        System.out.flush();
    }

Or some Stream operations can directly writeInt, we can call directly.

Pay attention to the use of read with array

InputStream has two read methods with arrays:

public int read(byte b[]) throws IOException

with

public int read(byte b[], int off, int len) throws IOException

If we use these two methods, we must pay attention to whether the read byte array is filled, consider the following example:

    public String wrongRead(InputStream in) throws IOException {
    
    
        byte[] data = new byte[1024];
        if (in.read(data) == -1) {
    
    
            throw new EOFException();
        }
        return new String(data, "UTF-8");
    }

If the InputStream data is not 1024, or the 1024 is not filled up due to network reasons, then we will get an array that is not filled up, so we actually have a problem with it.

How to use it correctly?

    public String readArray(InputStream in) throws IOException {
    
    
        int offset = 0;
        int bytesRead = 0;
        byte[] data = new byte[1024];
        while ((bytesRead = in.read(data, offset, data.length - offset))
                != -1) {
    
    
            offset += bytesRead;
            if (offset >= data.length) {
    
    
                break;
            }
        }
        String str = new String(data, 0, offset, "UTF-8");
        return str;
    }

We need to record the number of bytes actually read. By recording the offset, we get the final actual read result.

Or we can use the readFully method of DataInputStream to ensure that a complete byte array is read.

The little-endian and big-endian problems

Data in java is stored in big-endian by default, readByte(), readShort(), readInt(), readLong(), readFloat(), and readDouble() in DataInputStream are also big-endian by default If the data is read, there may be problems when interacting with other little-endian.

What we need is to convert little-endian to big-endian.

How to convert?

For example, if we want to read an int, we can first use the read method to read 4 bytes, and then convert the read 4 bytes from little-endian to big-endian.

    public void method1(InputStream inputStream) throws IOException {
    
    
        try(DataInputStream dis = new DataInputStream(inputStream)) {
    
    
            byte[] buffer = new byte[4];
            int bytesRead = dis.read(buffer);  // Bytes are read into buffer
            if (bytesRead != 4) {
    
    
                throw new IOException("Unexpected End of Stream");
            }
            int serialNumber =
                    ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
        }
    }

In the above example, we used the wrap and order methods provided by ByteBuffer to convert the Byte array.

Of course, we can also perform the conversion manually.

There is also the easiest way to call reverseBytes() after JDK1.5 to directly convert from little endian to big endian.

    public  int reverse(int i) {
    
    
        return Integer.reverseBytes(i);
    }

The code of this article:

learn-java-base-9-to-20/tree/master/security

This article has been included in http://www.flydean.com/java-security-code-line-file-io/

The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", know the technology, know you better!

Guess you like

Origin blog.csdn.net/superfjj/article/details/109306340