فهرست منبع

Colors: add a mechanism to resolve the ambiguity between the two ways of representing colors that were conflicting with each other due to mistakes made over the years in the community. Various fixes from Fuzzing

Miguel de Icaza 4 سال پیش
والد
کامیت
fcefefd991

+ 27 - 4
Sources/SwiftTerm/Buffer.swift

@@ -31,7 +31,11 @@ class Buffer {
         get { _yBase }
         set {
             if newValue > _lines.count {
-                //abort ()
+//                #if DEBUG
+//                abort ()
+//                #else
+//                return
+//                #endif
             }
             _yBase = newValue
         }
@@ -41,10 +45,14 @@ class Buffer {
     public var yDisp: Int {
         get { return _yDisp }
         set {
-            _yDisp = newValue
             if _yDisp < 0 {
+                #if DEBUG
                 abort()
+                #else
+                return
+                #endif
             }
+            _yDisp = newValue
         }
     }
     /**
@@ -57,7 +65,11 @@ class Buffer {
         get { return _x }
         set(newValue) {
             if newValue < 0 {
-                abort ()
+                #if DEBUG
+                abort()
+                #else
+                return
+                #endif
             }
             _x = newValue
         }
@@ -70,7 +82,11 @@ class Buffer {
         get { return _y }
         set(newValue) {
             if newValue < 0 {
+                #if DEBUG
                 abort()
+                #else
+                return
+                #endif
             }
             _y = newValue
         }
@@ -83,10 +99,14 @@ class Buffer {
     public var scrollBottom: Int {
         get { _scrollBottom }
         set {
-            _scrollBottom = newValue
             if newValue < 0 {
+                #if DEBUG
                 abort()
+                #else
+                return
+                #endif
             }
+            _scrollBottom = newValue
         }
     }
     
@@ -771,6 +791,9 @@ class Buffer {
 
             let lastLineLength = wrappedLines [wrappedLines.count - 1].getTrimmedLength ()
             let destLineLengths = getNewLineLengths (wrappedLines: wrappedLines, oldCols: oldCols, newCols: newCols)
+            if destLineLengths.count == 0 {
+                continue
+            }
             let linesToAdd = destLineLengths.count - wrappedLines.count
 
             var trimmedLines: Int

+ 10 - 1
Sources/SwiftTerm/EscapeSequenceParser.swift

@@ -293,6 +293,7 @@ class EscapeSequenceParser {
     // buffers over several calls
     var _osc: cstring
     var _pars: [Int]
+    var _parsTxt: [UInt8]
     var _collect: cstring
     var printHandler: PrintHandler = { (slice : ArraySlice<UInt8>) -> () in }
     var printStateReset: () -> () = {  }
@@ -304,6 +305,7 @@ class EscapeSequenceParser {
         table = EscapeSequenceParser.buildVt500TransitionTable()
         _osc = []
         _pars = [0]
+        _parsTxt = []
         _collect = []
         // "\"
         setEscHandler("\\", ParserEscHandlerFallback)
@@ -380,6 +382,7 @@ class EscapeSequenceParser {
         var osc = self._osc
         var collect = self._collect
         var pars = self._pars
+        var parsTxt = self._parsTxt
         var dcsHandler = activeDcsHandler
         
         //dump (data)
@@ -412,7 +415,7 @@ class EscapeSequenceParser {
                 // Prevent attempts at overflowing - crash 
                 let willOverflow =  newV > ((Int.max/10)-10)
                 pars [pars.count - 1] = willOverflow ? 0 : newV
-                
+                parsTxt.append(code)
                 i += 1
                 continue
             }
@@ -483,11 +486,13 @@ class EscapeSequenceParser {
             case .csiDispatch:
                 // Trigger CSI handler
                 if let handler = csiHandlers [code] {
+                    _parsTxt = parsTxt
                     handler (pars, collect)
                 } else {
                     csiHandlerFallback (pars, collect, code)
                 }
             case .param:
+                parsTxt.append(code)
                 if code == 0x3b || code == 0x3a {
                     pars.append (0)
                 } else {
@@ -512,6 +517,7 @@ class EscapeSequenceParser {
                 }
                 osc = []
                 pars = [0]
+                parsTxt = []
                 collect = []
                 dcs = -1
                 printStateReset()
@@ -537,6 +543,7 @@ class EscapeSequenceParser {
                 }
                 osc = []
                 pars = [0]
+                parsTxt = []
                 collect = []
                 dcs = -1
                 printStateReset()
@@ -584,6 +591,7 @@ class EscapeSequenceParser {
                 }
                 osc = []
                 pars = [0]
+                parsTxt = []
                 collect = []
                 dcs = -1
                 printStateReset()
@@ -601,6 +609,7 @@ class EscapeSequenceParser {
         _osc = osc
         _collect = collect
         _pars = pars
+        _parsTxt = parsTxt
         
         // save active dcs handler reference
         activeDcsHandler = dcsHandler

+ 0 - 1
Sources/SwiftTerm/Mac/MacTerminalView.swift

@@ -89,7 +89,6 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations {
     
     // These structures are parallel, maybe should be merged, but one contains the attributed text to render
     var attrStrBuffer: CircularList<ViewLineInfo>!
-
     // Attribute dictionary, maps a console attribute (color, flags) to the corresponding dictionary
     // of attributes for an NSAttributedString
     var attributes: [Attribute: [NSAttributedString.Key:Any]] = [:]

+ 78 - 78
Sources/SwiftTerm/Terminal.swift

@@ -1956,7 +1956,9 @@ open class Terminal {
         if buffer.y < buffer.scrollTop || buffer.y > buffer.scrollBottom {
             return
         }
-        var p = max (pars.count == 0 ? 1 : pars [0], 1)
+        // to prevent a Denial of Service
+        let maxLines = buffer._lines.maxLength * 2
+        var p = min (maxLines, max (pars.count == 0 ? 1 : pars [0], 1))
         let row = buffer.y + buffer.yBase
         
         let scrollBottomRowsOffset = rows - 1 - buffer.scrollBottom
@@ -2172,8 +2174,8 @@ open class Terminal {
             // We only support copying on the same page, and the page being 1
             if pars [4] == pars [7] && pars [4] == 1 {
                 if let (top, left, bottom, right) = getRectangleFromRequest(pars [0...3]) {
-                    let rowTarget = pars [5]-1
-                    let colTarget = pars [6]-1
+                    let rowTarget = min (rows-1, pars [5]-1)
+                    let colTarget = min (cols-1, pars [6]-1)
                     
                     // Block size
                     let columns = right-left+1
@@ -2942,6 +2944,67 @@ open class Terminal {
         let def = CharData.defaultAttr
 
         var i = 0
+        
+        // Extended Colors
+        //
+        // There is an ambiguity here that is troublesome, to support extended
+        // colors and colorspaces, two competing systems exists, one uses for example:
+        // 38;2;R;G;B;NEXT - foreground true color
+        // 38:2:ColorSpace:R:G:B:REST;NEXT - second style for the same
+        //
+        // The former apparently was a mistake, but we need to disambiguate the meaning
+        // of pars, based on whether the above uses ":" or ";" we need that, because
+        // the SGR is a collection of attributes, so after our parameter values, we
+        // need to continue processing
+        //
+        //
+        func parseExtendedColor () -> Attribute.Color? {
+            var color: Attribute.Color? = nil
+            let v = parser._parsTxt
+            
+            // If this is the new style
+            if v.count > 2 && v [2] == UInt8(ascii: ":") {
+                // Color style, we ignore "ColorSpace"
+                i += 1
+                if i+3 < parCount {
+                    color = Attribute.Color.trueColor(
+                          red: UInt8(min (pars [i+1], 255)),
+                        green: UInt8(min (pars [i+2], 255)),
+                         blue: UInt8(min (pars [i+3], 255)))
+                }
+                i += 4
+            } else {
+                switch pars [i] {
+                case 2: // RGB color
+                    i += 1
+                    if i+2 < parCount {
+                        color = Attribute.Color.trueColor(
+                              red: UInt8(min (pars [i], 255)),
+                            green: UInt8(min (pars [i+1], 255)),
+                             blue: UInt8(min (pars [i+2], 255)))
+                    }
+                    i += 3
+                    
+                case 3: // CMY color - not supported
+                    break
+                    
+                case 4: // CMYK color - not supported
+                    break
+                    
+                case 5: // indexed color
+                    if i+1 < parCount {
+                        fg = Attribute.Color.ansi256(code: UInt8 (min (255, pars [i+1])))
+                        i += 1
+                    }
+                    i += 1
+
+                default:
+                    break
+                }
+            }
+            return color
+        }
+        
         while i < parCount {
             var p = pars [i]
             switch p {
@@ -3003,44 +3066,11 @@ open class Terminal {
                 // fg color 8
                 fg = Attribute.Color.ansi256(code: UInt8(p - 30))
             case 38:
-                // Extended Foreground colors
-                if i+1 < parCount {
-                    switch pars [i+1] {
-                    case 2: // RGB color
-                        // Well this is a problem, if there are 3 arguments, expect R/G/B, if there are
-                        // more than 3, skip the first that would be the colorspace
-                        if i+5 < parCount {
-                            i += 1
-                        }
-                        if i+4 < parCount {
-                            fg = Attribute.Color.trueColor(
-                                  red: UInt8(min (pars [i+2], 255)),
-                                green: UInt8(min (pars [i+3], 255)),
-                                 blue: UInt8(min (pars [i+4], 255)))
-                        }
-                        // Given the historical disagreement that was caused by an ambiguous spec,
-                        // we eat all the remaining parameters.  At least until I can figure out if there
-                        i = parCount
-                        break
-                        
-                    case 3: // CMY color - not supported
-                        break
-                        
-                    case 4: // CMYK color - not supported
-                        break
-                        
-                    case 5: // indexed color
-                        if i+2 < parCount {
-                            fg = Attribute.Color.ansi256(code: UInt8 (min (255, pars [i+2])))
-                            i += 1
-                        }
-                        i += 1
-                        
-                    default:
-                        break
-                    }
+                i += 1
+                if let parsed = parseExtendedColor () {
+                    fg = parsed
                 }
-                
+                continue
             case 39:
                 // reset fg
                 fg = CharData.defaultAttr.fg
@@ -3048,44 +3078,12 @@ open class Terminal {
                 // bg color 8
                 bg = Attribute.Color.ansi256(code: UInt8(p - 40))
             case 48:
-                // Extended Background colors
-                if i+1 < parCount {
-                    // bg color 256
-                    switch pars [i+1] {
-                    case 2: // RGB color
-                        // Well this is a problem, if there are 3 arguments, expect R/G/B, if there are
-                        // more than 3, skip the first that would be the colorspace
-                        if i+5 < parCount {
-                            i += 1
-                        }
-                        if i+4 < parCount {
-                            bg = Attribute.Color.trueColor(
-                                red:   UInt8(min (255, pars [i+2])),
-                                green: UInt8(min (255, pars [i+3])),
-                                blue:  UInt8(min (255, pars [i+4])))
-                        }
-                        // Given the historical disagreement that was caused by an ambiguous spec,
-                        // we eat all the remaining parameters.  At least until I can figure out if there
-                        i = parCount
-                        break
-                        
-                    case 3: // CMY color - not supported
-                        break
-                        
-                    case 4: // CMYK color - not supported
-                        break
-                        
-                    case 5: // indexed color
-                        if i+2 < parCount {
-                            bg = Attribute.Color.ansi256(code: UInt8 (min (255, pars [i+2])))
-                            i += 1
-                        }
-                        i += 1
-
-                    default:
-                        break
-                    }
+                i += 1
+                if let parsed = parseExtendedColor() {
+                    bg = parsed
                 }
+                continue
+                
             case 49:
                 // reset bg
                 bg = CharData.defaultAttr.bg
@@ -3728,7 +3726,9 @@ open class Terminal {
     //
     func cmdRepeatPrecedingCharacter (_ pars: [Int], collect: cstring)
     {
-        let p = max (pars.count == 0 ? 1 : pars [0], 1)
+        // Maximum repeat, to avoid a denial of service
+        let maxRepeat = cols*rows*2
+        let p = min (maxRepeat, max (pars.count == 0 ? 1 : pars [0], 1))
         let line = buffer.lines [buffer.yBase + buffer.y]
         let chData = buffer.x - 1 < 0 ? CharData (attribute: CharData.defaultAttr) : line [buffer.x - 1]
         

+ 0 - 88
Sources/SwiftTermFuzz/main.swift

@@ -30,91 +30,3 @@ var queue = DispatchQueue(label: "Runner", qos: .userInteractive, attributes: .c
     t.feed (byteArray: arr)
     return 0
 }
-
-// For manually testing stuff and use the Xcode debugger
-func testInput (d: Data)
-{
-    let h = HeadlessTerminal (queue: queue) { exitCode in }
-    var data : [UInt8] = []
-    data.append(contentsOf: d)
-    let t = h.terminal!
-    t.silentLog = true
-    t.feed (byteArray: data)
-}
-
-func testCrashes ()
-{
-    let crashes = [
-        "crash-a58b5a38135bd7ffadad8b420ab8dcd0c3e4a1bd",
-        "crash-840102113e655342bfc30d2749406756a6e812d3",
-        "crash-654c8421b816426f584c3347a72cd2e869602ed5",
-        "crash-c6f850474ed073bb5b2e032c13d66819e68acc88",
-        "crash-a18a4cccc2a2b1c6f14ea804d15dd7f93682abf2",
-        "crash-b274a2639cd901a107778760708bb759c52086f8",
-        
-        "crash-9ff2abe9af46be74ca774b8d684e1df0737aa0bf",
-        "crash-fb6fa24871a603f7920dd24d467c449ac5b8d893",
-        "crash-f8e22628b8a2bb06d06fa9c064fe3a7363c35bde",
-        "crash-dc9cf799322b1223cb9a0e40283cb61812d50fbb",
-        "crash-c5c6e20dacfbb1a72599f8e135321a343d0dc2c6",
-        "crash-d38b59abae508cafa02c586d1706cf734977e6eb",
-        "crash-96e8d67b4e139f9eaebb950c5d25b7d5bd456359",
-        "crash-be2c5a1d40465efe36dd95771161829427dd6356",
-        "crash-3154715068e3ec98c7b425cc0fe56c1dbd1e1f58",
-        "crash-41df255a79feb00f8ded38dac7a51065a2758977",
-        "crash-45157239bc429db89546e2f0fea38b26f608b8d9",
-        
-        
-        "crash-2f5d273ae2f2bb95152905486bd9bfd8afa83c02",
-        "crash-17381a13c18b7bda011f260e38952fb8fb7e4616",
-        "crash-0a14a360e820c3801095d8bdbc130b3e18d55261",
-        "slow-unit-16849a4439a9ada62a289f64d122b3f898410375",
-        "slow-unit-be2210ab3bc792ab4932a65d0aecd1e3bbdb7db1",
-        "slow-unit-cb67af81341f405834d562bcb49570e2d3a95348",
-        "slow-unit-ff24adf923bfd5c9ccb4d58722c31c48d3f86480",
-
-        "crash-166a328f85dd916e1602764a1792f95b7d749a0e",
-        "crash-2b6b8631ea3cc418a069994de53e207da2d81230",
-        "crash-509b3b6f6b74c483c515eecb5114f2f21f7ca576",
-        "crash-698522a0a18e4fa7dcd0ee6b232d24cacecec07d",
-        "crash-6f6c2b5c064f8ef4510305a3e04b2ef2c646b731",
-        "crash-8876cfdf6927d0729ceccb6ae1c03da57c402eca",
-        "crash-b60503f1c280209282018547ac732afd6d735dd9",
-        "crash-ce788aaf77fa3219df3dea89f4a6771662402ebe",
-        "crash-cf873cabbbf89413ae6f40ffc3f87e452cb9ed9b",
-        "crash-e68d1073bb2f9140f95b4d9ad8dd076e5827f6c8",
-        "crash-e6a7981c673480824152b1f8c948fac0d4a294f4",
-
-        "crash-039a0b21c56b1e3a7a51056dd4f8daa9130c7312",
-        "crash-36eb1fbfdb3a61e7b17b166d190ffd85ad9c80ab",
-        "crash-4ba9dc95bc1c5d691fd9e80a4de72d65184e5c56",
-        "crash-59fb9d3b7ab81c1782d26dfc69a962fae49ec449",
-        "crash-64300317b2f97db7bfacfd77ba4d879e9726fd68",
-        "crash-b926cdde789b73ff9680ff9ab643f13fa36c0571",
-        "crash-c1147059ce893629e13289b43ae2b2ad1edcf44f",
-        "crash-de2a0b4222547592208f7f85e2cd5b2730194daa",
-        "crash-e1f2f0f2ef07d6d728316fa1bc336e6d1d699b99",
-        "crash-ec47d21af677ee8eb18f91e150cdfb5d41d931c1",
-
-        
-    ]
-    
-    for crash in crashes {
-        let url = URL(fileURLWithPath: "/Users/miguel/cvs/SwiftTerm/\(crash)")
-        let data: Data
-        do {
-            print ("Running test \(crash)")
-            data = try Data(contentsOf: url)
-        } catch {
-            print ("Caught error loading \(crash)")
-            continue
-        }
-
-        testInput (d: data)
-        
-        print ("passed crash \(crash)")
-    }
-    print ("Happy!")
-}
-
- //testCrashes()

+ 112 - 1
Tests/SwiftTermTests/FuzzerTests.swift

@@ -1,8 +1,119 @@
 //
-//  File.swift
+// FuzzerTests: runs the tests that feed the fuzzer inputs
 //  
 //
 //  Created by Miguel de Icaza on 4/29/21.
 //
 
 import Foundation
+import XCTest
+import Foundation
+
+@testable import SwiftTerm
+
+final class FuzzerTests: XCTestCase {
+    var queue = DispatchQueue(label: "Runner", qos: .userInteractive, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
+    
+    // For manually testing stuff and use the Xcode debugger
+    func testInput (d: Data)
+    {
+        let h = HeadlessTerminal (queue: queue) { exitCode in }
+        var data : [UInt8] = []
+        data.append(contentsOf: d)
+        let t = h.terminal!
+        t.silentLog = true
+        t.feed (byteArray: data)
+    }
+
+    // These do not really test crashes, they are just slow:
+    //        "slow-unit-0c2689cd9f79cf89245ff42a5c312ebf0742e5be",
+    //        "slow-unit-16849a4439a9ada62a289f64d122b3f898410375",
+    //        "slow-unit-be2210ab3bc792ab4932a65d0aecd1e3bbdb7db1",
+    //        "slow-unit-cb67af81341f405834d562bcb49570e2d3a95348",
+    //        "slow-unit-ff24adf923bfd5c9ccb4d58722c31c48d3f86480",
+
+    func test (_ crash: String) {
+        var file: String
+        let t1 = "/Users/miguel/cvs/SwiftTerm/\(crash)"
+        let t2 = "/Users/miguel/cvs/SwiftTermFuzzerResults/\(crash)"
+        
+        if FileManager.default.fileExists(atPath: t1) {
+            file = t1
+        } else if FileManager.default.fileExists(atPath: t2) {
+            file = t2
+        } else {
+            print ("Data file \(crash) not found in the peer directory or this directory")
+            return
+        }
+        let url = URL(fileURLWithPath: file)
+        let data: Data
+        do {
+            print ("Running test \(crash)")
+            data = try Data(contentsOf: url)
+        } catch {
+            print ("Failure to load the data file \(crash)")
+            return
+        }
+
+        testInput (d: data)
+    }
+    
+    func testCrashes ()
+    {
+        return 
+        test ("crash-661c9f1d29d682c0d7fd640fa57266b24c9a8ed2")
+        test ("crash-a455aeceaf7374464ee888fbf85691ef91ab6480")
+        test ("crash-a58b5a38135bd7ffadad8b420ab8dcd0c3e4a1bd")
+        test ("crash-840102113e655342bfc30d2749406756a6e812d3")
+        test ("crash-654c8421b816426f584c3347a72cd2e869602ed5")
+        test ("crash-c6f850474ed073bb5b2e032c13d66819e68acc88")
+        test ("crash-a18a4cccc2a2b1c6f14ea804d15dd7f93682abf2")
+        test ("crash-b274a2639cd901a107778760708bb759c52086f8")
+        test ("crash-9ff2abe9af46be74ca774b8d684e1df0737aa0bf")
+        test ("crash-fb6fa24871a603f7920dd24d467c449ac5b8d893")
+        test ("crash-f8e22628b8a2bb06d06fa9c064fe3a7363c35bde")
+        test ("crash-dc9cf799322b1223cb9a0e40283cb61812d50fbb")
+        test ("crash-c5c6e20dacfbb1a72599f8e135321a343d0dc2c6")
+        test ("crash-d38b59abae508cafa02c586d1706cf734977e6eb")
+        test ("crash-96e8d67b4e139f9eaebb950c5d25b7d5bd456359")
+        test ("crash-be2c5a1d40465efe36dd95771161829427dd6356")
+        test ("crash-3154715068e3ec98c7b425cc0fe56c1dbd1e1f58")
+        test ("crash-41df255a79feb00f8ded38dac7a51065a2758977")
+        test ("crash-45157239bc429db89546e2f0fea38b26f608b8d9")
+        test ("crash-2f5d273ae2f2bb95152905486bd9bfd8afa83c02")
+        test ("crash-17381a13c18b7bda011f260e38952fb8fb7e4616")
+        test ("crash-0a14a360e820c3801095d8bdbc130b3e18d55261")
+        test ("crash-166a328f85dd916e1602764a1792f95b7d749a0e")
+        test ("crash-2b6b8631ea3cc418a069994de53e207da2d81230")
+        test ("crash-509b3b6f6b74c483c515eecb5114f2f21f7ca576")
+        test ("crash-698522a0a18e4fa7dcd0ee6b232d24cacecec07d")
+        test ("crash-6f6c2b5c064f8ef4510305a3e04b2ef2c646b731")
+        test ("crash-8876cfdf6927d0729ceccb6ae1c03da57c402eca")
+        test ("crash-b60503f1c280209282018547ac732afd6d735dd9")
+        test ("crash-ce788aaf77fa3219df3dea89f4a6771662402ebe")
+        test ("crash-cf873cabbbf89413ae6f40ffc3f87e452cb9ed9b")
+        test ("crash-e68d1073bb2f9140f95b4d9ad8dd076e5827f6c8")
+        test ("crash-e6a7981c673480824152b1f8c948fac0d4a294f4")
+        test ("crash-039a0b21c56b1e3a7a51056dd4f8daa9130c7312")
+        test ("crash-36eb1fbfdb3a61e7b17b166d190ffd85ad9c80ab")
+        test ("crash-4ba9dc95bc1c5d691fd9e80a4de72d65184e5c56")
+        test ("crash-59fb9d3b7ab81c1782d26dfc69a962fae49ec449")
+        test ("crash-64300317b2f97db7bfacfd77ba4d879e9726fd68")
+        test ("crash-b926cdde789b73ff9680ff9ab643f13fa36c0571")
+        test ("crash-c1147059ce893629e13289b43ae2b2ad1edcf44f")
+        test ("crash-de2a0b4222547592208f7f85e2cd5b2730194daa")
+        test ("crash-e1f2f0f2ef07d6d728316fa1bc336e6d1d699b99")
+        test ("crash-ec47d21af677ee8eb18f91e150cdfb5d41d931c1")
+    }
+    
+    func testTimeouts ()
+    {
+        test ("timeout-44d56090b5e02248f1d90d2ff371d27abaae532f")
+        test ("timeout-8244fb7b31c904aff447c0456cebd79688f142db")
+    }
+    
+    static var allTests = [
+        ("testFuzzerCrashes", testCrashes),
+        ("testFuzzerTimeouts", testTimeouts)
+    ]
+}