忽略使用file.readline(size)之后读取的其余行

时间:2018-07-13 16:22:55

标签: python security

有问题

我有一个Python应用程序,它将部署在各个地方。因此Nasty先生很可能会修改该应用。

因此,问题与安全性有关。该应用程序将接收从远程来源收到的文件(纯文本)。该设备的RAM(Raspberry Pi)数量非常有限。

很有可能向脚本提供超大的输入,这将是一个大麻烦。
我想避免按原样读取文件的每一行,而只读取限于例如该行的第一部分。 44个字节,其余部分忽略。

因此,仅出于此目的,一个非常原始的示例:

lines = []
with open("path/to/file.txt", "r") as fh:
    while True:
        line = fh.readline(44)
        if not line:
            break
        lines.append(line)

这有效,但是如果一行的长度超过44个字符,则下一个读取将是该行的其余部分,或同一行的多个44字节长的部分。 演示:

print(lines)
['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
 'aaaaaaaaaaaaaaaaaaaaaaaaa \n', 
 '11111111111111111111111111111111111111111111', 
 '111111111111111111111111111111111111111\n', 
 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', 
 'bbbbbbbbbbbbbbb\n', 
 '22222222222222222222222222222222222222222\n',
 'cccccccccccccccccccccccccccccccccccccccccccc', 
 'cccccccccccccccccccccccccccccccccccccccccccc', 
 'cccc\n', 
 '333333333333\n', 
 'dddddddddddddddddddd\n']

这不会使我免于将全部内容读取到变量中,并可能导致产生整洁的 DOS

我认为也许使用file.next()会跳到下一行。

lines = []
with open("path/to/file.txt", "r") as fh:
    while True:
        line = fh.readline(44)
        if not line:
            break   
        if line != "":
            lines.append(line.strip())
            fh.next()

但这会引发错误:

Traceback (most recent call last):
  File "./test.py", line 7, in <module>
    line = fh.readline(44)
ValueError: Mixing iteration and read methods would lose data

...对此我无能为力。 我已经读过file.seek(),但实际上并没有像文档那样强大的功能。

同时,我在写这篇文章,实际上是我自己想出来的。如此简单,几乎令人尴尬。但是我认为我将完成本文,然后将其留给可能有相同问题的其他人。

所以我的解决方案:

lines = []
with open("path/to/file.txt", "r") as fh:
    while True:
        line = fh.readline(44)
        if not line:
            break
        lines.append(line)
        if '\n' not in line:
            fh.readline()

所以输出现在看起来像这样:

print(lines)
['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'11111111111111111111111111111111111111111111',
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
'22222222222222222222222222222222222222222\n',
'cccccccccccccccccccccccccccccccccccccccccccc',
'333333333333\n',
'dddddddddddddddddddd\n']

足够近了。

我不敢说这是最好的或很好的解决方案,但它似乎可以完成工作,而且我根本没有将行的多余部分存储在变量中。

但是出于好奇,我实际上有一个问题。 如上所述:

fh.readline()

当您调用这样的方法而没有将其输出重定向到变量或其他方法时,该方法将输入存储在哪里,它的寿命是什么(我的意思是,如果将其完全存储,什么时候将被销毁)?

谢谢大家的投入。我学到了一些有用的东西。

我不太喜欢file.read(n)的工作方式,即使大多数解决方案都依赖它。

感谢你们,我提出了一个仅使用file.readline(n)的原始解决方案的改进解决方案:

limit = 10
lineList = []
with open("linesfortest.txt", "rb") as fh:
    while True:

        line = fh.readline(limit)
        if not line:
            break

        if line.strip() != "":
            lineList.append(line.strip())
        while '\n' not in line:
            line = fh.readline(limit)

print(lineList)

如果我的想法是正确的,则内部while循环将读取该行的相同块,直到读取EOL字符为止,同时,它将一次又一次仅使用大小变量。 并提供输出:

['"Alright,"', 
 '"You\'re re', 
 '"Tell us!"', 
 '"Alright,"', 
 'Question .', 
 '"The Answe', 
 '"Yes ...!"', 
 '"Of Life,', 
 '"Yes ...!"', 
 '"Yes ...!"', 
 '"Is ..."', 
 '"Yes ...!!', 
 '"Forty-two'] 

根据

的内容
"Alright," said the computer and settled into silence again. The two men fidgeted. The tension was unbearable.
"You're really not going to like it," observed Deep Thought.
"Tell us!"
"Alright," said Deep Thought.
Question ..."
"The Answer to the Great
"Yes ...!"
"Of Life, the Universe and Everything ..." said Deep Thought
"Yes ...!" "Is ..." said Deep Thought, and paused.
"Yes ...!"
"Is ..."
"Yes ...!!!...?"
"Forty-two," said Deep Thought, with infinite majesty and calm.

3 个答案:

答案 0 :(得分:2)

当您这样做时:

f.readline()

从文件中读取一行,然后分配一个字符串,返回然后丢弃。

如果行数很大,即使不存储该值,也可以通过调用f.readline()(在某些文件损坏时发生)来耗尽内存(在分配/重新分配阶段)

限制行的大小是可行的,但是如果再次调用f.readline(),则会得到行的其余部分。诀窍是跳过剩余的字符,直到找到行终止字符。一个简单的独立示例,说明如何做:

max_size = 20
with open("test.txt") as f:
    while True:
        l = f.readline(max_size)
        if not l:
            break   # we reached the end of the file
        if l[-1] != '\n':
            # skip the rest of the line
            while True:
                c = f.read(1)
                if not c or c == "\n":  # end of file or end of line
                    break
        print(l.rstrip())

该示例读取一行的开始,并且如果该行已被截断(即,它没有以行终止的方式结束),我将读取其余行,并将其丢弃。即使该行很长,也不会占用内存。真是太慢了。

关于组合next()readline():它们是并发机制(手动迭代与经典行读取),并且不能混用,因为一种方法的缓冲可能被另一种方法忽略。但您可以混合使用read()readline()for循环和next()

答案 1 :(得分:1)

尝试这样:

'''
$cat test.txt 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
'''
from time import sleep # trust me on this one

lines = []
with open("test.txt", "r") as fh:
    while True:
        line = fh.readline(44)
        print (line.strip())
        if not line:
            #sleep(0.05)
            break
        lines.append(line.strip())
        if not line.endswith("\n"):
            while fh.readline(1) != "\n":
                pass
print(lines)

非常简单,它将读取44个字符,如果它没有以新行结尾,则它将一次读取1个字符,直到到达它以避免大块内存进入内存为止,然后它将继续处理下一个44个字符。字符并将其附加到列表中。

当字符串少于44个字符时,请不要忘记使用line.strip()来避免将\n作为字符串的一部分。

答案 2 :(得分:1)

我假设您在这里问的是您的原始问题,而不是关于临时值(Jean-François Fabre has already answered nicely)的附带问题。

您现有的解决方案实际上并不能解决您的问题。

比方说,您的攻击者创建的行长为1亿个字符。所以:

  • 您执行一个fh.readline(44),它会读取前44个字符。
  • 然后,您执行fh.readline()来丢弃其余的行。必须将此行的其余部分读入一个字符串中以丢弃它,因此它占用了100MB。

您可以通过循环读取一次直到'\n'的一个字符来解决这个问题,但是有一个更好的解决方案:循环fh.readline(44)直到'\n'。也许fh.readline(8192)之类的东西—暂时浪费8KB(实际上是一遍又一遍地使用8KB)并不能帮助您的攻击者。

例如:

while True:
    line = fh.readline(20)
    if not line:
        break
    lines.append(line.strip())
    while line and not line.endswith('\n'):
        line = fh.readline(8192)

在实践中,这不会更加有效。 Python 2.x文件对象包装了一个C stdio FILE,该对象已经具有缓冲区,并且默认参数为open,它是平台所选择的缓冲区。假设您的平台使用16KB。

因此,无论您是read(1)还是readline(8192),它实际上是一次从磁盘读取16KB到某个隐藏的缓冲区中,并且只是从该缓冲区中复制1或8192个字符到Python字符串中。 / p>

而且,虽然循环16384次并构建16384个微小的字符串显然要比循环两次并构建两个8K字符串花费更多的时间,但该时间可能仍小于磁盘I / O时间。

因此,如果您更好地理解read(1)代码,并且可以更轻松地调试和维护它,则只需这样做。


但是,这里可能有更好的解决方案。如果您使用的是64位平台,或者最大的文件容量小于2GB(或者大于2GB的文件甚至在处理之前就引发了错误是可以接受的),则可以mmap文件,然后搜索它,就好像它是内存中的巨大字符串一样:

from contextlib import closing
import mmap

lines = []
with open('ready.py') as f:
    with closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)) as m:
        start = 0
        while True:
            end = m.find('\n', start)
            if end == -1:
                lines.append(m[start:start+44])
                break
            lines.append(m[start:min(start+44, end)])
            start = end + 1

这会将整个文件映射到虚拟内存,但是大部分虚拟内存未映射到物理内存。您的操作系统将自动根据需要自动调入和调出页面,以使其完全适合您的资源。 (而且,如果您担心“交换地狱”:换出已经由磁盘文件支持的未修改页面本质上是瞬时的,所以这不是问题。)

例如,假设您有一个1GB的文件。在具有16GB RAM的笔记本电脑上,到结束时可能会将整个文件映射到1GB的连续内存中,但这也可能很好。在具有128MB RAM的资源受限的系统上,它将开始丢弃最近最少使用的页面,最后仅将文件的最后几页映射到内存中,这也很好。唯一的区别是,如果您随后尝试进行print m[0:100],则笔记本电脑将能够立即执行此操作,而嵌入式包装盒将不得不将第一页重新加载到内存中。由于您没有通过文件进行这种随机访问,因此不会出现这种情况。